对于文件名扩展,“find”实用程序的“-name”选项似乎功能类似,但与 bash shell 的内置模式匹配并不完全相同。
以下是 GNU 参考手册的相关部分:
- Bash shell 模式匹配:http://www.gnu.org/software/bash/manual/bashref.html#Pattern-Matching
- “查找”实用程序模式匹配:http://www.gnu.org/software/findutils/manual/html_mono/find.html#Shell-Pattern-Matching
这本身就非常令人困惑。为了增加这种混乱,“find”实用程序手册页(上面引用)的第 2.1.4 节标题为“Shell 模式匹配”,这意味着“find”正在使用 shell 的内置模式匹配功能。然而,情况似乎并非如此,因为根据“查找”手册页(http://goo.gl/ngQTKx),在“-name 模式”下,内容如下:
“文件名匹配是使用 fnmatch(3) 库函数执行的。不要忘记将模式括在引号中,以防止它被 shell 扩展。”
由此看来,似乎不是 shell 执行模式匹配,而是使用 fnmatch 库的 find 实用程序。
这是我的问题:
- bash shell 的默认文件名扩展和模式匹配(禁用 extglob shell 选项)是否与使用 -name 选项的 find 实用程序不同?
- 如果是这样,这些差异是什么?
- bash 是否还使用 fnmatch 库或其他一些机制来进行文件名扩展和模式匹配?
答案1
在shell中,需要区分文件名生成/扩展(又名通配:扩展为文件列表的模式)模式匹配。通配用途内部模式匹配,但实际上首先是操作员产生基于的文件列表图案。
*/*.txt
是一个图案它匹配 0 个或多个字符的序列,后跟/
,后跟零个或多个字符的序列,后跟.txt
。当用作外壳模式时,如下所示:
case $file in
*/*.txt) echo match
esac
它将匹配于file=.foo/bar/baz.txt
.
然而,*/*.txt
作为一个全局是相关但更复杂的东西。
在扩展*/*.txt
为文件列表时,shell 将打开当前目录,列出其内容,查找匹配的目录类型(或目录的符号链接)的非隐藏文件*
,对该列表进行排序,打开每个目录,列出其内容,并找到与 匹配的非隐藏的*.txt
。
即使它与模式匹配,它也永远不会扩展,.foo/bar/bar.txt
因为这不是它的工作原理。另一方面,由 a 生成的文件路径全局都会匹配该模式。
类似地,glob like将查找目录中foo[a/b]baz*
名称以 开头的所有文件。b]baz
foo[a
因此,我们已经看到,对于通配符而言,但对于模式匹配而言,/
这是特殊的(通配符以某种方式分割/
,并且每个部分单独处理)并且点文件被特殊处理。
shell 通配符和模式匹配是 shell 语法的一部分。它与引用和其他形式的扩展交织在一起。
$ bash -c 'case "]" in [x"]"]) echo true; esac'
true
引用]
删除其特殊含义(关闭前一个[
):
当你混合所有东西时,它可能会更加混乱:
$ ls
* \* \a x
$ p='\*' ksh -xc 'ls $p'
+ ls '\*' '\a'
\* \a
OK\*
是所有以\
.
$ p='\*' bash -xc 'ls $p'
+ ls '\*'
\*
并不是所有以\
.所以,不知何故,\
一定已经逃脱了*
,但话又说回来,它也不匹配*
......
对于find来说,就简单多了。find
在它收到的每个文件参数处沿着目录树下降,然后按照指示对每个遇到的文件进行测试。
对于-type f
,即真的如果该文件是常规文件,错误的否则对于-name <some-pattern>
,如果姓名当前考虑的文件的 与模式匹配,否则为 false。这里没有隐藏文件或/
处理或 shell 引用的概念,只是将字符串(文件名)与模式进行匹配。
例如,-name '*foo[a/b]ar'
(将-name
和*foo[a/b]ar
参数传递给find
)将匹配foobar
和.fooaar
。它永远不会匹配foo/bar
,但那是因为-name
文件名匹配;它会-path
代替。
现在,有一种引用/转义的形式——为了find
-- 此处已识别,并且仅带有反斜杠。这允许逃避操作员。对于 shell,它是作为通常 shell 引用的一部分完成的(\
是 shell 的引用机制之一)。对于find
( fnmatch()
),这是模式语法的一部分。
例如,-name '\**'
将匹配名称以*
.-name '*[\^x]*'
将匹配名称包含^
或x
...的文件
现在,对于find
、和各种fnmatch()
其他bash
shell 识别的不同运算符,它们至少应该在一个公共子集上达成一致:*
、?
和[...]
。
特定的 shell 或find
实现是否使用系统的fnmatch()
函数或它们自己的函数取决于实现。 GNUfind
至少在 GNU 系统上是这样的。 Shell 不太可能使用它们,因为这会使事情变得复杂并且不值得付出努力。
bash
当然不是。像 ksh、bash、zsh 这样的现代 shell 还对*
、?
、[...]
以及许多选项和特殊参数 ( GLOBIGNORE
/ FIGNORE
) 进行了扩展,以影响它们的通配行为。
另请注意,除了fnmatch()
实现 shell 模式匹配之外,还有glob()
实现类似于 shell 通配符的功能。
现在,这些不同实现中的模式匹配运算符之间可能存在细微的差异。
例如,对于 GNU fnmatch()
、?
、*
或[!x]
不会匹配不形成有效字符的字节或字节序列,而bash
(和大多数其他 shell)会匹配。例如,在 GNU 系统上,find . -name '*'
可能无法匹配名称包含无效字符的文件,而bash -c 'echo *'
会列出它们(只要它们不以 开头.
)。
我们已经提到过引用可能引起的混乱。