将 glob 转换为 `find`

Ole*_*nge 11 find wildcards

我一次又一次地遇到这个问题:我有一个 glob,它与正确的文件完全匹配,但导致Command line too long. 每次我都将它转换为find和 的某种组合时,grep这适用于特定情况,但这不是 100% 等效的。

例如:

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
Run Code Online (Sandbox Code Playgroud)

是否有将 glob 转换为find我不知道的表达式的工具?或者是否有find匹配 glob 而不匹配子目录中相同 glob的选项(例如foo/*.jpg,不允许匹配bar/foo/*.jpg)?

mur*_*uru 15

如果问题是您收到参数列表太长错误,请使用循环或内置 shell。虽然command glob-that-matches-too-much可以出错,for f in glob-that-matches-too-much但不会出错,所以你可以这样做:

for f in foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
do
    something "$f"
done
Run Code Online (Sandbox Code Playgroud)

循环可能会非常缓慢,但它应该可以工作。

或者:

printf "%s\0" foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg |
  xargs -r0 something
Run Code Online (Sandbox Code Playgroud)

printf在大多数 shell 中内置,以上可以解决execve()系统调用的限制)

$ cat /usr/share/**/* > /dev/null
zsh: argument list too long: cat
$ printf "%s\n" /usr/share/**/* | wc -l
165606
Run Code Online (Sandbox Code Playgroud)

也适用于 bash。我不确定这到底是在哪里记录的。


Vimglob2regpat()和 Pythonfnmatch.translate()都可以将 glob 转换为正则表达式,但也都使用.*for *,匹配/.

  • 可以通过 `exec` 传递的参数有限制,适用于诸如 `cat` 等外部命令;但该限制不适用于 shell 内置命令,例如 `printf`。 (4认同)

Sté*_*las 9

find(对于-name/-path标准谓词)像{a,b}glob一样使用通配符模式(请注意,这不是 glob 运算符;展开后,您会得到两个 glob)。主要区别在于斜杠的处理(以及在 中没有特别处理的点文件和目录find)。*在 globs 中不会跨越多个目录。*/*/*将导致最多 2 个级别的目录被列出。添加 a-path './*/*/*'将匹配至少 3 级深度的任何文件,并且不会停止find列出任何深度的任何目录的内容。

对于那个特别

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
Run Code Online (Sandbox Code Playgroud)

几个 globs,很容易翻译,你想要深度 3 的目录,所以你可以使用:

find . -mindepth 3 -maxdepth 3 \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +
Run Code Online (Sandbox Code Playgroud)

(或-depth 3一些find实现)。或者POSIXly:

find . -path './*/*/*' -prune \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +
Run Code Online (Sandbox Code Playgroud)

这将保证那些*?无法匹配的/字符。

( find,与 globs 相反,它会读取foo*bar当前目录中除目录之外的目录的内容¹ ,而不是对文件列表进行排序。但是,如果我们抛开与无效字符匹配的内容[A-Z]*/的行为的问题?是未指定,您将获得相同的文件列表)。

但无论如何,正如@muru 所表明的那样find如果只是将文件列表拆分为多个运行以解决execve()系统调用的限制,则无需求助。一些像zsh(with zargs) 或ksh93(with command -x) 这样的 shell 甚至有内置的支持。

zsh(其 glob 也具有等效于-type f和大多数其他find谓词),例如:

autoload zargs # if not already in ~/.zshrc
zargs ./foo*bar/quux[A-Z](|.bak)/pic[0-9][0-9][0-9][0-9]?.jpg(.) -- cmd
Run Code Online (Sandbox Code Playgroud)

((|.bak)是一个 glob 运算符与{,.bak}, (.)glob 限定符等效于find's -type f,oN在那里添加以跳过排序find,D以包含点文件 (不适用于此 glob))


¹ 要find像 globs 一样抓取目录树,您需要以下内容:

find . ! -name . \( \
  \( -path './*/*' -o -name 'foo*bar' -o -prune \) \
  -path './*/*/*' -prune -name 'pic[0-9][0-9][0-9][0-9]?.jpg' -exec cmd {} + -o \
  \( ! -path './*/*' -o -name 'quux[A-Z]' -o -name 'quux[A-Z].bak' -o -prune \) \)
Run Code Online (Sandbox Code Playgroud)

修剪除 1 级目录以外的所有目录,修剪foo*barquux[A-Z]or目录外的所有 2级目录,quux[A-Z].bak然后选择pic...级别 3 的目录(并修剪该级别的所有目录)。