我有一个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
Cherry Hill和纽约市被视为2或3个单独的条目.
我尝试引用文件名,如下所示:
for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do echo $f done
但无济于事.
必须有一个简单的方法来做到这一点.
以下答案很棒.但为了使这更复杂 - 我并不总是想使用我的测试目录中列出的目录.有时我想将目录名称作为命令行参数传递.
我接受了查尔斯关于设置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
Charles Duff.. 103
首先,不要这样做.最好的方法是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
如果您的查找不支持-print0
,那么您的结果就是不安全的 - 如果文件中存在包含其名称中的换行符(以下是合法的),则以下内容将无法按预期运行:
# this is unsafe while IFS= read -r n; do printf '%q\n' "$n" done < <(find test -mindepth 1 -type d)
如果不打算使用上述方法之一,第三种方法(在时间和内存使用方面效率较低,因为它在进行分词之前读取子进程的整个输出)是使用一个IFS
变量不包含空格字符.关闭通配符(set -f
),以防止含水珠字符,例如字符串[]
,*
或?
从被扩展:
# 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
我以前从未见过$'\n'语法.这是如何运作的?(我原以为IFS ='\n'或IFS ="\n"都可以使用,但两者都没有.) (2认同)
Johannes Sch.. 26
find . -type d | while read file; do echo $file; done
但是,如果文件名包含换行符,则不起作用.当你真正想要在变量中使用目录名时,以上是我所知道的唯一解决方案.如果您只想执行某些命令,请使用xargs.
find . -type d -print0 | xargs -0 echo 'The directory is: '
@Charles:对于大量文件,xargs效率更高:它只产生一个进程.-exec选项为每个文件分配一个新进程,速度可能慢一个数量级. (4认同)
亚当,不,'+'会聚合尽可能多的文件名,然后执行.但它没有像并行运行这样的整洁功能:) (2认同)
请注意,如果您想对文件名执行某些操作,则必须引用它们.例如:`找到.-type d | 同时读取文件; 做ls"$ file"; done` (2认同)
cbliard.. 22
这是一个处理文件名中的制表符和/或空格的简单解决方案.如果你必须处理文件名中的其他奇怪字符,如换行符,请选择另一个答案.
测试目录
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
如果你使用的是zsh,你也可以这样做:`alias duc ='du -sh*(/)'` (2认同)
Jonathan Lef.. 7
这在标准的Unix中非常棘手,并且大多数解决方案都会违反换行符或其他一些字符.但是,如果您使用的是GNU工具集,那么您可以利用该find
选项-print0
并使用xargs
相应的选项-0
(减零).有两个字符不能出现在简单的文件名中; 那些是斜线和NUL'\ 0'.显然,斜杠出现在路径名中,因此使用NUL'\ 0'标记名称末尾的GNU解决方案是巧妙且简单的.
首先,不要这样做.最好的方法是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
如果您的查找不支持-print0
,那么您的结果就是不安全的 - 如果文件中存在包含其名称中的换行符(以下是合法的),则以下内容将无法按预期运行:
# this is unsafe while IFS= read -r n; do printf '%q\n' "$n" done < <(find test -mindepth 1 -type d)
如果不打算使用上述方法之一,第三种方法(在时间和内存使用方面效率较低,因为它在进行分词之前读取子进程的整个输出)是使用一个IFS
变量不包含空格字符.关闭通配符(set -f
),以防止含水珠字符,例如字符串[]
,*
或?
从被扩展:
# 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
find . -type d | while read file; do echo $file; done
但是,如果文件名包含换行符,则不起作用.当你真正想要在变量中使用目录名时,以上是我所知道的唯一解决方案.如果您只想执行某些命令,请使用xargs.
find . -type d -print0 | xargs -0 echo 'The directory is: '
这是一个处理文件名中的制表符和/或空格的简单解决方案.如果你必须处理文件名中的其他奇怪字符,如换行符,请选择另一个答案.
测试目录
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中非常棘手,并且大多数解决方案都会违反换行符或其他一些字符.但是,如果您使用的是GNU工具集,那么您可以利用该find
选项-print0
并使用xargs
相应的选项-0
(减零).有两个字符不能出现在简单的文件名中; 那些是斜线和NUL'\ 0'.显然,斜杠出现在路径名中,因此使用NUL'\ 0'标记名称末尾的GNU解决方案是巧妙且简单的.