如何在zsh循环中避免使用空格作为分隔符?

5
我正在尝试编写一个脚本,将音乐库中的一些文件进行转换。
但是如果我像这样做:
#!/usr/bin/zsh                                                                     

for x in $(find -name "*.m4a");
do
    echo $x;
done

在包含以下内容的文件夹中进行解释:
foo\ bar.m4a

它将返回:
foo
bar.m4a

我该如何防止for循环将空格字符解释为分隔符?
我可以用$(find -name "*.m4a" | sed "s/ /_/g")替换$(find -name "*.m4a"),然后在循环内部使用反向的sed,但是如果文件名/路径已经包含下划线(或其他我可能用作下划线替代的字符)呢?
有什么想法吗?

1
使用for循环在“find”输出上会引起人们的注意。如果可以的话,使用find的内部-exec功能在使用它时执行命令。该功能在find实现中是可移植的,并有助于避免解析“for”循环和文件名空格时遇到的问题。话虽如此,人们可以设置IFS变量以对shell用于标记化的分隔符进行细粒度控制。通常,IFS的最简单用法就是将其设置为空,就像这样:IFS= - user4832408
3个回答

7

您可以通过双引号来防止命令替换中的单词拆分:

#!/usr/bin/zsh                                                                     

for x in "$(find . -name "*.m4a")"
do
    echo "$x"
done

注意命令替换中的双引号不会与外部的双引号冲突(我以前从未注意到这一点)。如果您觉得更易读,也可以使用单引号,例如"$(find -name '*.m4a')"。我通常在find primaries中使用单引号。

循环体内的引用对于相同的原因非常重要。它将确保将x的值作为单个参数传递。

但这绝对是一个混合的、弗兰肯斯坦式的解决方案,循环遍历find的输出通常被认为是不安全的。您最好使用shell文件名生成或按以下方式使用find

find . -name '*.mp3' -exec echo {} \;

您可以添加额外的-exec主语句,它们将像由&&分隔的shell命令一样执行。

如果您绝对必须循环遍历find输出,请使用空字符分隔的输出而不是换行符分隔的输出:

touch 'a b.txt'
touch $'b\nc.txt'
touch $'d\te.txt'

find . -name '*.txt' -print0 \
| while IFS= read -r -d $'\0' fname; do
    printf '%s\n\n' "$fname"
done

4

在这里使用 zsh 的globbing功能,而不是使用find

for x in **/*.m4a; do
    echo "$x"
done

zsh 的默认设置下,在循环体中引用$x 是可选的,但无论如何这样做都不是坏主意。


好的!我没有想到这个。 - vmonteco

2

2
这对于文件名包含换行符的文件无效(很少见,但是合法的)。 - chepner
@chepner文件名中有换行符吗?我老了。 - Jonathan
@Jonathan 我不确定是否曾经有过它们是非法的时候;人们只是避免使用它们以使代码更易于编写。 - chepner
如果您坚持要循环遍历“find”输出,请至少使用“-print0”和“IFS =”,以处理空值分隔符而不是换行符分隔符。 - shadowtalker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接