MCS*_*MCS 117 bash whitespace loops
我有一个bash shell脚本循环遍历某个目录的所有子目录(但不是文件).问题是某些目录名称包含空格.
以下是我的测试目录的内容:
$ls -F test
Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt
Run Code Online (Sandbox Code Playgroud)
以及遍历目录的代码:
for f in `find test/* -type d`; do
echo $f
done
Run Code Online (Sandbox Code Playgroud)
这是输出:
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
Run Code Online (Sandbox Code Playgroud)
但无济于事.
必须有一个简单的方法来做到这一点.
以下答案很棒.但为了使这更复杂 - 我并不总是想使用我的测试目录中列出的目录.有时我想将目录名称作为命令行参数传递.
我接受了查尔斯关于设置IFS的建议,并提出了以下建议:
dirlist="${@}"
(
[[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
for d in $dirlist; do
echo $d
done
)
Run Code Online (Sandbox Code Playgroud)
除非命令行参数中有空格(即使引用了这些参数),这也可以正常工作.例如,像这样调用脚本:test.sh "Cherry Hill" "New York City"产生以下输出:
Cherry Hill New York City
Cha*_*ffy 103
首先,不要这样做.最好的方法是find -exec正确使用:
# this is safe
find test -type d -exec echo '{}' +
Run Code Online (Sandbox Code Playgroud)
另一种安全方法是使用NUL终止列表,但这需要您的查找支持-print0:
# this is safe
while IFS= read -r -d '' n; do
printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)
Run Code Online (Sandbox Code Playgroud)
您还可以从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
Run Code Online (Sandbox Code Playgroud)
如果您的查找不支持-print0,那么您的结果就是不安全的 - 如果文件中存在包含其名称中的换行符(以下是合法的),则以下内容将无法按预期运行:
# this is unsafe
while IFS= read -r n; do
printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)
Run Code Online (Sandbox Code Playgroud)
如果不打算使用上述方法之一,第三种方法(在时间和内存使用方面效率较低,因为它在进行分词之前读取子进程的整个输出)是使用一个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
)
Run Code Online (Sandbox Code Playgroud)
最后,对于命令行参数的情况,如果你的shell支持它们,你应该使用数组(即它是ksh,bash或zsh):
# this is safe
for d in "$@"; do
printf '%s\n' "$d"
done
Run Code Online (Sandbox Code Playgroud)
将保持分离.请注意,引用(以及使用$@而不是$*)很重要.数组也可以用其他方式填充,例如glob表达式:
# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
printf '%s\n' "$d"
done
Run Code Online (Sandbox Code Playgroud)
Joh*_*itb 26
find . -type d | while read file; do echo $file; done
Run Code Online (Sandbox Code Playgroud)
但是,如果文件名包含换行符,则不起作用.当你真正想要在变量中使用目录名时,以上是我所知道的唯一解决方案.如果您只想执行某些命令,请使用xargs.
find . -type d -print0 | xargs -0 echo 'The directory is: '
Run Code Online (Sandbox Code Playgroud)
cbl*_*ard 22
这是一个处理文件名中的制表符和/或空格的简单解决方案.如果你必须处理文件名中的其他奇怪字符,如换行符,请选择另一个答案.
测试目录
ls -F test
Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt
Run Code Online (Sandbox Code Playgroud)
进入目录的代码
find test -type d | while read f ; do
echo "$f"
done
Run Code Online (Sandbox Code Playgroud)
"$f"如果用作参数,则必须引用文件名().如果没有引号,则空格充当参数分隔符,并且为调用的命令提供多个参数.
并输出:
test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia
Run Code Online (Sandbox Code Playgroud)
这在标准的Unix中非常棘手,并且大多数解决方案都会违反换行符或其他一些字符.但是,如果您使用的是GNU工具集,那么您可以利用该find选项-print0并使用xargs相应的选项-0(减零).有两个字符不能出现在简单的文件名中; 那些是斜线和NUL'\ 0'.显然,斜杠出现在路径名中,因此使用NUL'\ 0'标记名称末尾的GNU解决方案是巧妙且简单的.
您可以暂时使用 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
Run Code Online (Sandbox Code Playgroud)
<!>