我一次又一次地遇到这个问题:我有一个 glob,它与正确的文件完全匹配,但导致Command line too long
.每次我都会将其转换为适用于特定情况的find
和grep
的某种组合,但并不是 100% 等效。
例如:
./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
是否有一个工具可以将 glob 转换为find
我不知道的表达式?或者是否有一个选项可以find
匹配 glob 而不匹配子目录中的相同 glob (例如foo/*.jpg
不允许 match bar/foo/*.jpg
)?
答案1
如果问题是您收到参数列表太长错误,请使用循环或内置 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
该循环可能非常慢,但它应该可以工作。
或者:
printf "%s\0" foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg |
xargs -r0 something
(printf
大多数 shell 中都内置了上述内容,因此可以解决系统调用的限制execve()
)
$ cat /usr/share/**/* > /dev/null
zsh: argument list too long: cat
$ printf "%s\n" /usr/share/**/* | wc -l
165606
也适用于 bash。我不确定这到底记录在哪里。
两个 Vim 的glob2regpat()
和Python的fnmatch.translate()
可以将 glob 转换为正则表达式,但两者也都使用.*
for *
,跨/
.
答案2
find
(对于-name
/-path
标准谓词)使用通配符模式,就像 glob 一样(请注意,这{a,b}
不是 glob 运算符;扩展后,您会得到两个 glob)。主要区别是斜杠的处理(并且点文件和目录在 中没有被特殊处理find
)。*
in globs 不会跨越多个目录。*/*/*
将导致列出最多 2 级目录。添加 a-path './*/*/*'
将匹配至少 3 级深度的任何文件,并且不会停止find
列出任何深度的任何目录的内容。
对于那个特定的
./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
几个 glob,很容易翻译,你想要深度为 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 {} +
或者 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 {} +
这将保证那些*
和?
无法匹配/
字符。
( find
,与 glob 相反,会读取除当前目录中的目录之外的目录内容foo*bar
,并且不会对文件列表进行排序。但是,如果我们忽略/匹配的内容或关于无效字符[A-Z]
的行为的问题是未指定,您将获得相同的文件列表)。*
?
但无论如何,作为@muru 已表明find
,如果只是为了将文件列表拆分为多次运行来解决execve()
系统调用的限制,则无需求助。一些 shell,例如zsh
(with zargs
) 或ksh93
(with command -x
) 甚至对此有内置支持。
With 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
((|.bak)
是与 相反的 glob 运算符{,.bak}
,(.)
glob 限定符相当于find
's -type f
,添加oN
到其中可以跳过像 with 那样的排序find
,D
以包含点文件(不适用于此 glob))
1 为了find
像 glob 一样爬行目录树,你需要类似的东西:
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 \) \)
那是修剪除 之外的所有级别 1 的目录,以及除或foo*bar
之外的所有级别 2 的目录,然后选择级别 3 的目录(并修剪该级别的所有目录)。quux[A-Z]
quux[A-Z].bak
pic...
答案3
您可以编写一个正则表达式来查找符合您要求的内容:
find . -regextype egrep -regex './foo[^/]*bar/quux[A-Z](\.bak)?/pic[0-9][0-9][0-9][0-9][^/]?\.jpg'
答案4
关于注释的概括我的另一个答案,作为对您问题的更直接回答,您可以使用此 POSIXsh
脚本将 glob 转换为find
表达式:
#! /bin/sh -
glob=${1#./}
shift
n=$#
p='./*'
while true; do
case $glob in
(*/*)
set -- "$@" \( ! -path "$p" -o -path "$p/*" -o -name "${glob%%/*}" -o -prune \)
glob=${glob#*/} p=$p/*;;
(*)
set -- "$@" -path "$p" -prune -name "$glob"
while [ "$n" -gt 0 ]; do
set -- "$@" "$1"
shift
n=$((n - 1))
done
break;;
esac
done
find . "$@"
配合使用一标准sh
glob (所以不是示例中使用的两个 glob大括号扩展):
glob2find './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' \
-type f -exec cmd {} +
.
(除了和之外,不会忽略点文件或点目录,..
并且不会对文件列表进行排序)。
该方法仅适用于相对于当前目录的全局变量,没有.
或..
组件。通过一些努力,您可以将其扩展到任何 glob,不仅仅是一个 glob...也可以对其进行优化,以便不会像模式那样glob2find 'dir/*'
寻找相同的内容。dir