在 bash 中,如果我们激活extglob
shell 选项,我们可以做一些奇特的事情,比如否定 glob(来自man bash
):
If the extglob shell option is enabled using the shopt builtin, sev\xe2\x80\x90\n eral extended pattern matching operators are recognized. In the fol\xe2\x80\x90\n lowing description, a pattern-list is a list of one or more patterns\n separated by a |. Composite patterns may be formed using one or more\n of the following sub-patterns:\n\n ?(pattern-list)\n Matches zero or one occurrence of the given patterns\n *(pattern-list)\n Matches zero or more occurrences of the given patterns\n +(pattern-list)\n Matches one or more occurrences of the given patterns\n @(pattern-list)\n Matches one of the given patterns\n !(pattern-list)\n Matches anything except one of the given patterns\n
Run Code Online (Sandbox Code Playgroud)\n例如:
\n$ ls\na1file a2file b1file b2file\n\n$ shopt -s extglob\n$ ls !(a*)\nb1file b2file\n
Run Code Online (Sandbox Code Playgroud)\n现在,如果我只想对目录执行某些操作,我可以使用*/
:
$ ls -F\na1file a2file adir/ b1file b2file bdir/\n\n$ ls -d */\nadir/ bdir/\n
Run Code Online (Sandbox Code Playgroud)\n然而,glob*/
显然不能被否定:
$ ls -Fd !(*/)\na1file a2file adir/ b1file b2file bdir/\n
Run Code Online (Sandbox Code Playgroud)\n是什么赋予了?为什么正确仅包含目录时不!(*/)
排除目录?*/
有没有办法在 bash 中使用 glob 来排除目录?
上述命令是在 Arch Linux 系统上使用 GNU bash 版本 5.1.8(1)-release 进行测试的。
\nSté*_*las 12
因为球体不会跨越/
边界。除了**/
\xc2\xb9 的特殊情况(最初来自zsh
,现在在设置选项(对于 bash)后也经常在其他一些 shell 中找到shopt -s globstar
),glob 运算符无法匹配包含 a 的内容,/
因为它们应用于目录列表。
shellx/y/z
在 s 上分裂了一个球体/
。对于每个组件,如果该组件包含 glob 运算符,则 shell 会列出父目录并再次匹配每个条目的模式,如果没有,则仅查找带有lstat()
\xc2\xb2 的文件。
您会看到a*b/c
不会匹配a/b/c
。shell 仅匹配a*b
当前目录中的条目。Even[a/b]*
被视为[a
并b]*
用 分隔/
。
*/
是*
且没有任何东西与 分开/
。这是一种特殊情况,*/x
shell 首先查找*
当前目录列表中匹配的所有文件,然后对于每个文件,尝试查看是否file/x
存在名为 的文件(在这种情况下使用,而不是像那样lstat()
列出目录)x
不包含全局运算符)。与*/
它相同,只是它检查 a 是否file/
存在(仅当file
是目录或目录的符号链接时才是正确的)。
如果您/
在 ksh-style @(...)
, !(...)
... 扩展运算符(其中的子集在bash -O extglob
or中可用zsh -o kshglob
)内使用,则 shell 之间的行为会有所不同,但通常不会执行您想要的操作,因为 glob 中的模式仅与文件匹配目录列表中的名称。在bash
,中!(*/)
匹配每个(非隐藏)文件名,可能是因为这里的 glob 没有在 上分割/
,并且 是*/
针对每个目录条目名称进行反向检查,并且目录条目名称不能包含/
. 这并不能真正解释为什么!(*[a/b]*)
仍然包含包含a
s 或b
s 的文件名,或者为什么!(*[a")"/b])
排除包含a
s 的文件名而不包含包含)
s 或 s 的文件名b
s 的文件名。
如果您想要在符号链接解析后未确定为目录类型的文件,那么您不能单独使用 glob 来完成此操作,您需要使用zsh
及其 glob 限定符,它可以真正根据其他属性选择文件比他们的名字:
print -rC1 -- *(-^/)\n
Run Code Online (Sandbox Code Playgroud)\n在这里,zsh 匹配 glob,然后应用限定符作为 globbing 后的额外步骤。这里-
指定在符号链接解析之后应用以下限定符(stat()
而不是lstat()
),^
否定以下限定符,选择目录/
类型的文件。
在bash
4.4+ 中,您始终可以将作业外包给其他打印 NUL 分隔结果并用于readarray -td \'\'
获取结果的工具,例如:
readarray -td \'\' files < <(zsh -c \'print -rNC1 -- *(N^-.)\')\n(( ${#files[@]} )) && ls -Fd -- "${files[@]}"\n
Run Code Online (Sandbox Code Playgroud)\n或者使用 GNUfind
和sort
:
readarray -td \'\' files < <(\n LC_ALL=C find . -mindepth 1 -maxdepth 1 \\\n ! -name \'.*\' ! -xtype d -printf \'%P\\0\' | sort -z)\n(( ${#files[@]} )) && ls -Fd -- "${files[@]}"\n
Run Code Online (Sandbox Code Playgroud)\n(这里对 进行排序以sort
获得与 相同的列表zsh
,尽管对于将该列表传递给 的特殊情况,它和它自己的排序ls
一样是多余的)。ls
当您有一个 NUL 分隔列表时,您也可以跳过数组步骤并将输出传递给xargs -r0 ls -Fd --
,这将避免必须特殊处理空列表情况并解决arg 列表太长的限制。
\xc2\xb9 不过,另请参阅~
扩展 glob 运算符,zsh
它可以作为完整 glob 之后的额外步骤应用,以过滤出路径并跨/
s 进行匹配。在 中a*/b*/c*~*e*
,对 glob 执行文件名生成算法a*/b*/c*
,然后使用模式过滤出生成的路径名*e*
。
\xc2\xb2 不区分大小写的通配可以改变这一点,尽管像zsh -o nocaseglob