我有一个bash shell脚本循环通过某个目录的所有子目录(但不是文件)。问题是一些目录名称包含空格。
这里是我的测试目录的内容:
$ls -F test Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt
和循环通过目录的代码:
for f in `find test/* -type d`; do echo $f done
这里是输出:
test/Baltimore test/Cherry Hill test/Edison test/New York City test/Philadelphia
樱桃山和纽约市被视为2或3个单独的条目。
我试着引用文件名,像这样:
for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do echo $f done
但无济于事。
有一个简单的方法来做到这一点。
下面的答案是伟大的。但是为了使这更复杂 – 我不总是想使用我的test目录中列出的目录。有时候,我想将目录名称作为命令行参数传递。
我采取了Charles的建议设置IFS,并提出以下:
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”生成以下输出:
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)
您还可以从find中填充数组,并稍后传递该数组:
# 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
如果你的find不支持-print0,你的结果是不安全的 – 如果文件存在包含换行符的名称(这是,是合法的),下面的行为将不会如所期望的:
# this is unsafe while IFS= read -r n; do printf '%q\n' "$n" done < <(find test -mindepth 1 -type d)
如果一个人不打算使用上述之一,第三种方法(在时间和内存使用方面效率较低,因为它在字分割之前读取子过程的整个输出)是使用IFS变量不包含空格字符。关闭globbing(设置-f)以防止包含glob字符的字符串,如[],*或?从扩展:
# 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
将保持分离。注意,引用(和使用$ @而不是$ *)很重要。数组也可以通过其他方式填充,如glob表达式:
# this is safe entries=( test/* ) for d in "${entries[@]}"; do printf '%s\n' "$d" done