将glob转换为`find`


11

我一次又一次遇到这个问题:我有一个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)?


展开大括号,您应该可以将结果表达式与-path或一起使用-ipathfind . -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg'应该工作-除了匹配/fooz/blah/bar/quuxA/pic1234d.jpg。那会是个问题吗?
muru

是的,那将是一个问题。它必须是100%等效的。
Ole Tange

问题是我们不知道,到底有什么区别。您的模式还可以。
peterh-恢复莫妮卡

我已将您的扩展程序帖子添加为该问题的答案。我希望它还不错。
彼得-恢复莫妮卡

您不能做echo <glob> | cat,假设我对bash有所了解,echo是内置的,因此没有最大命令限制
Ferrybig

Answers:


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

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

要么:

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转换为正则表达式,但也都可以.*用于*,匹配/


如果这是真的,那么更换somethingecho应该做到这一点。
奥莱·丹吉

1
@OleTange这就是我建议的原因printf-它比调用echo数千次要快,并且提供了更大的灵活性。
muru

4
可传递的参数有一个限制exec,它适用于外部命令,例如cat; 但是该限制不适用于shell内置命令,例如printf
斯蒂芬·基特

1
@OleTange该行不太长,因为printf它是内置函数,并且外壳程序可能使用与为其枚举参数相同的方法来为其提供参数forcat不是内置的。
muru

1
从技术上讲,有像mkshwhere printf不是内置的shell和像ksh93where cat是(或可以是)内置的shell。另请参见zargszsh解决它,而不必求助于xargs
斯特凡Chazelas

9

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

对于那个特殊的

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

几个问题,翻译起来很容易,您想要目录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 {} +

(或-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 {} +

这将保证那些*和字符?不能匹配/

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

但是无论如何,正如@muru所示find如果只是将文件列表分成多个运行以解决execve()系统调用的限制,则无需求助。某些外壳(如zsh(带有zargs)或ksh93(带有command -x))甚至对此具有内置支持。

zsh(其glob也具有-type ffind谓词等价的谓词),例如:

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限定符与finds 等效-type foN在其中添加以跳过与的排序findD包括点文件(不适用于此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 \) \)

也就是说,修剪所有级别为1的目录(除去目录)foo*bar,以及所有级别2的除quux[A-Z]quux[A-Z].bak,然后选择pic...级别3的目录(并修剪该级别的所有目录)。


3

您可以编写一个正则表达式来查找符合您要求的内容:

find . -regextype egrep -regex './foo[^/]*bar/quux[A-Z](\.bak)?/pic[0-9][0-9][0-9][0-9][^/]?\.jpg'

有没有一种工具可以进行这种转换以避免人为错误?
奥莱·丹吉

否,但只改变我做了逃跑.,添加可选的匹配.bak和变化*,以[^/]*不匹配的路径一样/富/富/酒吧等
sebasth

但是,即使您的转换也是错误的。?不会更改为[^ /]。这正是我要避免的人为错误。
Ole Tange

1
我认为使用egrep,您可以缩短[0-9][0-9][0-9][0-9]?[0-9]{3,4}
wjandrea


0

概括一下我的其他答案的注释,作为对问题的更直接答案,您可以使用此POSIX sh脚本将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 . "$@"

要与一个标准shglob 一起使用(而不是示例中使用大括号扩展的两个glob ):

glob2find './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' \
  -type f -exec cmd {} +

(除了.和不忽略点文件或点目录,..并且不对文件列表进行排序)。

那只适用于相对于当前目录的glob,没有.或没有任何..组件。通过一些努力,您可以将其扩展到任何glob,而不是glob。。。还可以对其进行优化,以使glob2find 'dir/*'查找dir的样式与模式不同。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.