我如何逃避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)
如果不打算使用上述之一,则第三种方法(在时间和内存使用方面效率较低,因为它在进行分词之前读取子处理的整个输出)是使用IFS
variables不包含空格字符。 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(复制,没有转码)…这就像上面的时间,注意,而不是只读文件 '会逃脱。