我刚刚读完一篇文章,标题为“为什么你不应该解析 ls 的输出”。我想循环一个目录中的每个文件,文章指出以下是最基本、最理想的方式之一。
for f in *; do
echo $f
# …do stuff
done
我不明白的一件事是我怎样才能忽略某些文件和目录?我用的是ZSH并且已经开启extendedglob
。因此,我认为这是可行的:for f in *^.jpg; do…
,但它只回显“*^.jpg”,没有文件。
如何保持循环基本不变,但具有排除某些模式的能力?额外问题:你能让 glob 递归吗?
答案1
Zsh 提供了非常好的枚举文件的方法。他们被记录下来在手册中的“文件名生成”下,但是 zsh 手册不太容易遵循。
默认情况下,如果没有匹配的文件,您将收到错误,这在命令行中通常是可取的,但在脚本中则不然。要禁用此错误,请将其放在脚本的开头附近以打开null_glob
选项:
setopt null_glob
…
for x in *; do …
或使用N
全局限定符:
# Reliably iterate over non-dot files
for x in *(N); do …
上面的任一代码片段都会迭代当前目录中不存在的所有文件点文件。如果没有这样的文件,则循环根本不会运行(与 sh 不同,循环体在未展开的 glob 上毫无帮助地运行一次*
,而默认的 zsh 则 glob 导致错误)。
要在迭代中包含点文件,请打开got_glob
选项或使用D
glob 限定符。
# Reliably iterate over all files in the current directory
for x in *(DN); do …
我建议使用 glob 限定符方法,即使它更冗长,因为即使您将该代码段复制粘贴到未打开相同选项的脚本中,它也会保持相同的行为方式。
要根据名称排除某些文件,您可以使用全局运算符 ^
和~
。请注意,这些需要setopt extended_glob
在您的脚本²中(这不是从您的运行时环境或您的.zshrc
¹ 继承的)。例如,要排除*.jpg
文件,请写入*~*.jpg
或^*.jpg
.
(请注意,*^.jpg
默认情况下,您编写的内容会在 zsh 中导致错误。如果您看到*^.jpg
打印出来,则要么您在关闭状态下运行 zsh nomatch
(默认情况下处于打开状态),这通常仅在模拟 sh 时完成或 ksh,否则您实际上是在 sh shell 下运行该脚本,可能是因为它缺少舍邦线在顶部。)
setopt extended_glob
# Iterate over all the files in the current directory except *.jpg and .*
for x in *~*.jpg(N); do …
如果你想递归地遍历子目录,很简单:只需使用**/
。
setopt extended_glob
# Iterate over all the files in the current directory and its (grand-)*children except *.jpg and .*
for x in **/*~*.jpg(N); do …
通过全局限定符,您还可以根据名称以外的条件选择文件。例如,仅循环常规文件,不包括目录、符号链接等:
# Iterate over all regular files in the current directory and its (grand-)*children except *.jpg and .*
for x in **/*(.DN); do …
您甚至可以运行任意代码来决定是否使用e
或+
glob 限定符包含文件。但是,当在 for 循环中使用通配符模式时,将过滤代码放在循环体的顶部会更清晰。
1您可以输入setopt extended_glob
,~/.zshenv
但我强烈建议不要使用.zshenv
,因为任何对其中内容进行假设的脚本都会在不同的计算机或不同的帐户上崩溃。
²如果您正在编写 zsh 补全函数,这些函数将在extended_glob
打开的环境中运行。