我如何逃避bash循环列表中的空白?

我有一个bash shell脚本循环通过某个目录的所有子目录(但不是文件)。 问题是一些目录名称包含空格。

这里是我的testing目录的内容:

$ls -F test Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt 

以及通过目录循环的代码:

 for f in `find test/* -type d`; do echo $f done 

这是输出:

testing/巴尔的摩
testing/樱桃
爬坡道
testing/爱迪生 
testing/新
纽约
市
testing/费城

樱桃山和纽约市被视为2或3个单独的条目。

我尝试引用文件名,如下所示:

 for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do echo $f done 

但无济于事。

有一个简单的方法来做到这一点。


下面的答案是很好的。 但是为了使这更复杂 – 我不总是要使用我的testing目录中列出的目录。 有时我想将目录名称作为命令行参数传入。

我接受了查尔斯关于设置IFS的build议,并提出了以下build议:

 dirlist="${@}" ( [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n' for d in $dirlist; do echo $d done ) 

这工作得很好,除非命令行参数中有空格(即使引用了这些参数)。 例如,像这样调用脚本: test.sh "Cherry Hill" "New York City"产生以下输出:

樱桃
爬坡道
新
纽约
市

首先,不要那样做。 最好的方法是正确使用find -exec

 # this is safe find test -type d -exec echo '{}' + 

另一种安全的方法是使用NUL终止列表,虽然这需要你的支持-print0

 # this is safe while IFS= read -r -d '' n; do printf '%q\n' "$n" done < <(find test -mindepth 1 -type d -print0) 

您也可以从查找中填充数组,然后再传递该数组:

 # this is safe declare -a myarray while IFS= read -r -d '' n; do myarray+=( "$n" ) done < <(find test -mindepth 1 -type d -print0) printf '%q\n' "${myarray[@]}" # printf is an example; use it however you want 

如果您的查找不支持-print0 ,则结果是不安全的 – 如果文件名中包含换行符(这是合法的),那么下面的行为将不会如您-print0

 # this is unsafe while IFS= read -rn; do printf '%q\n' "$n" done < <(find test -mindepth 1 -type d) 

如果不打算使用上述之一,则第三种方法(在时间和内存使用方面效率较低,因为它在进行分词之前读取子处理的整个输出)是使用IFSvariables不包含空格字符。 closuresglobbing( set -f )以防止包含[]*?等全局字符的string? 从扩展:

 # this is unsafe (but less unsafe than it would be without the following precautions) ( IFS=$'\n' # split only on newlines set -f # disable globbing for n in $(find test -mindepth 1 -type d); do printf '%q\n' "$n" done ) 

最后,对于命令行参数的情况,你应该使用数组,如果你的shell支持它们(即它是ksh,bash或者zsh):

 # this is safe for d in "$@"; do printf '%s\n' "$d" done 

将保持分离。 请注意,引用(以及使用$@而不是$* )非常重要。 数组也可以以其他方式填充,例如globexpression式:

 # this is safe entries=( test/* ) for d in "${entries[@]}"; do printf '%s\n' "$d" done 
 find . -type d | while read file; do echo $file; done 

但是,如果文件名包含换行符,则不起作用。 以上是我知道的唯一的解决scheme,当你真的想在一个variables的目录名称。 如果你只是想执行一些命令,使用xargs。

 find . -type d -print0 | xargs -0 echo 'The directory is: ' 

这是一个简单的解决scheme,它处理文件名中的制表符和/或空格。 如果你必须处理文件名中其他奇怪的字符,如换行符,请select另一个答案。

testing目录

 ls -F test Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt 

进入目录的代码

 find test -type d | while read f ; do echo "$f" done 

如果用作参数,则文件名必须加引号( "$f" )。 没有引号,空格作为参数分隔符和多个参数给被调用的命令。

而输出:

 test/Baltimore test/Cherry Hill test/Edison test/New York City test/Philadelphia 

这在标准的Unix中是非常棘手的,而且大多数解决scheme都会遇到换行符或其他字符的问题。 但是,如果您正在使用GNU工具集,则可以利用find选项-print0并使用带相应选项-0 (minus-zero)的xargs 。 有两个字符不能出现在一个简单的文件名; 那些是斜线和NUL'\ 0'。 显然,斜杠出现在path名中,所以使用NUL'\ 0'来标记名称的末尾的GNU解决scheme是巧妙的,而且是傻瓜式的。

为什么不放?

 IFS='\n' 

在for命令前面? 这将字段分隔符从<Space> <Tab> <Newline>更改为<Newline>

我用

 SAVEIFS=$IFS IFS=$(echo -en "\n\b") for f in $( find "$1" -type d ! -path "$1" ) do echo $f done IFS=$SAVEIFS 

这不够吗?
来自http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html的想法;

不要将列表存储为string; 将它们存储为数组以避免所有这些分隔符的混淆。 以下是一个脚本示例,它可以在所有testing子目录上运行,也可以在命令行上提供列表:

 #!/bin/bash if [ $# -eq 0 ]; then # if no args supplies, build a list of subdirs of test/ dirlist=() # start with empty list for f in test/*; do # for each item in test/ ... if [ -d "$f" ]; then # if it's a subdir... dirlist=("${dirlist[@]}" "$f") # add it to the list fi done else # if args were supplied, copy the list of args into dirlist dirlist=("$@") fi # now loop through dirlist, operating on each one for dir in "${dirlist[@]}"; do printf "Directory: %s\n" "$dir" done 

现在,让我们在一个testing目录中试试这条曲线:

 $ ls -F test Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ this is a dirname with quotes, lfs, escapes: "\''?'?\e\n\d/ this is a file, not a directory $ ./test.sh Directory: test/Baltimore Directory: test/Cherry Hill Directory: test/Edison Directory: test/New York City Directory: test/Philadelphia Directory: test/this is a dirname with quotes, lfs, escapes: "\'' ' \e\n\d $ ./test.sh "Cherry Hill" "New York City" Directory: Cherry Hill Directory: New York City 
 find . -print0|while read -d $'\0' file; do echo "$file"; done 

PS,如果它只是在input空间,那么一些双引号对我来说工作顺利…

 read artist; find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \; 

您可以使用IFS(内部字段分隔符)临时使用:

 OLD_IFS=$IFS # Stores Default IFS IFS=$'\n' # Set it to line break for f in `find test/* -type d`; do echo $f done $IFS=$OLD_IFS 

要添加到Jonathan所说的内容:使用-print0选项与xargs一起find ,如下所示:

 find test/* -type d -print0 | xargs -0 command 

这将执行具有适当参数的命令command ; 带有空格的目录将被正确引用(即它们将作为一个参数传入)。

 #!/bin/bash dirtys=() for folder in * do if [ -d "$folder" ]; then dirtys=("${dirtys[@]}" "$folder") fi done for dir in "${dirtys[@]}" do for file in "$dir"/\*.mov # <== *.mov do #dir_e=`echo "$dir" | sed 's/[[:space:]]/\\\ /g'` -- This line will replace each space into '\ ' out=`echo "$file" | sed 's/\(.*\)\/\(.*\)/\2/'` # These two line code can be written in one line using multiple sed commands. out=`echo "$out" | sed 's/[[:space:]]/_/g'` #echo "ffmpeg -i $out_e -sameq -vcodec msmpeg4v2 -acodec pcm_u8 $dir_e/${out/%mov/avi}" `ffmpeg -i "$file" -sameq -vcodec msmpeg4v2 -acodec pcm_u8 "$dir"/${out/%mov/avi}` done done 

上面的代码将.mov文件转换为.avi。 .mov文件位于不同的文件夹中,文件夹名称也有空格 。 我上面的脚本会将.mov文件转换为.avi文件在同一个文件夹本身。 我不知道它是否有助于你的人民。

案件:

 [sony@localhost shell_tutorial]$ ls Chapter 01 - Introduction Chapter 02 - Your First Shell Script [sony@localhost shell_tutorial]$ cd Chapter\ 01\ -\ Introduction/ [sony@localhost Chapter 01 - Introduction]$ ls 0101 - About this Course.mov 0102 - Course Structure.mov [sony@localhost Chapter 01 - Introduction]$ ./above_script ... successfully executed. [sony@localhost Chapter 01 - Introduction]$ ls 0101_-_About_this_Course.avi 0102_-_Course_Structure.avi 0101 - About this Course.mov 0102 - Course Structure.mov [sony@localhost Chapter 01 - Introduction]$ CHEERS! 

干杯!

不得不用path名来处理空格。 我最后做的是使用recursion和for item in /path/*

 function recursedir { local item for item in "${1%/}"/* do if [ -d "$item" ] then recursedir "$item" else command fi done } 

将文件列表转换成Bash数组。 这使用马特McClure的方法从Bash函数返回一个数组: http : //notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html结果是一种方式将任何多行input转换为Bash数组。

 #!/bin/bash # This is the command where we want to convert the output to an array. # Output is: fileSize fileNameIncludingPath multiLineCommand="find . -mindepth 1 -printf '%s %p\\n'" # This eval converts the multi-line output of multiLineCommand to a # Bash array. To convert stdin, remove: < <(eval "$multiLineCommand" ) eval "declare -a myArray=`( arr=(); while read -r line; do arr[${#arr[@]}]="$line"; done; declare -p arr | sed -e 's/^declare -a arr=//' ) < <(eval "$multiLineCommand" )`" for f in "${myArray[@]}" do echo "Element: $f" done 

这种方法即使在出现错误的字符时也可以工作,并且是将任何input转换为Bash数组的一般方法。 缺点是如果input很长,可能会超出Bash的命令行大小限制,或者占用大量的内存。

方法最终在列表中工作的循环也有列表pipe道有阅读stdin不容易(如要求用户input)的缺点,循环是一个新的过程,所以你可能想知道为什么variables循环结束后,您在循环中设置的内容不可用。

我也不喜欢设置IFS,它可以搞乱其他代码。

只是发现我和你的问题有一些相似之处。 Aparrently如果你想传递参数的命令

 test.sh "Cherry Hill" "New York City" 

按顺序打印出来

 for SOME_ARG in "$@" do echo "$SOME_ARG"; done; 

注意$ @被双引号包围, 这里有一些注释

我需要相同的概念从一个特定的文件夹顺序压缩几个目录或文件。 我已经解决了使用awk从lsparsing列表并避免名称中的空格问题。

 source="/xxx/xxx" dest="/yyy/yyy" n_max=`ls . | wc -l` echo "Loop over items..." i=1 while [ $i -le $n_max ];do item=`ls . | awk 'NR=='$i'' ` echo "File selected for compression: $item" tar -cvzf $dest/"$item".tar.gz "$item" i=$(( i + 1 )) done echo "Done!!!" 

你怎么看?

 find Downloads -type f | while read file; do printf "%q\n" "$file"; done 

那么,我看到太多复杂的答案。 我不想传递查找工具的输出或写一个循环,因为find有“exec”选项。

我的问题是,我想将所有带有dbf扩展名的文件移动到当前文件夹中,其中一些文件包含空格。

我这样解决:

  find . -name \*.dbf -print0 -exec mv '{}' . ';' 

对我来说看起来很简单

对我来说这是有效的,而且非常“干净”:

 for f in "$(find ./test -type d)" ; do echo "$f" done 

只是有一个简单的变体的问题…将types.flv的文件转换为.mp3(打哈欠)。

 for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done 

recursion地find所有的Macintosh用户的Flash文件,并把它们变成audio(复制,没有转码)…这就像上面的时间,注意,而不是只读文件 '会逃脱。