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