命令(即 grep)如何知道它何时作为 glob 扩展的一部分运行?

cod*_*rus 4 command-line bash wildcards

根据我的理解,一个 glob 通配符由 shell 解释,然后为每个匹配的文件名运行给定的命令。假设我有文件:abc1, abc2, and abc3在我的当前目录中。然后,例如,echo abc*将为每个以 'abc' 开头的文件名回显一次。

但是,如果我运行grep 'foo' abc*,我想这应该运行:

grep 'foo' abc1
grep 'foo' abc2
grep 'foo' abc3
Run Code Online (Sandbox Code Playgroud)

这意味着我应该得到以下输出(假设所有文件都包含一行显示“foo”):

foo
foo
foo
Run Code Online (Sandbox Code Playgroud)

然而,我得到:

abc1:foo
abc2:foo
abc3:foo
Run Code Online (Sandbox Code Playgroud)

所以我认为对此有两种可能的解释。首先,grep 以某种方式可以检测到它是否与 glob 表达式一起使用,并通过在匹配之前输出文件名来响应。其次,由于您可以将多个文件传递给 grep,shell 实际上只运行 1 个命令:

grep 'foo' abc1 abc2 abc3

但是,这只有效,因为 grep 最后接受多个文件。另一个命令可能只允许传入 1 个文件。因此,如果您想为与 glob 匹配的多个文件运行该命令,则如果通过上述第二种方法进行 globbing 工作,它将无法工作。

无论如何,有人可以对此有所了解吗?

谢谢!

Ser*_*nyy 5

这就是诀窍:命令不知道,它是完成这项工作的外壳

例如考虑grep 'abc' *.txt。如果我们运行系统调用跟踪,您将看到如下内容:

bash-4.3$ strace -e trace=execve grep "abc" *.txt > /dev/null
execve("/bin/grep", ["grep", "abc", "ADDA_converters.txt", "after.txt", "altera_license.txt", "altera.txt", "ANALOG_DIGITAL_NOTES.txt", "androiddev.txt", "answer2.txt", "answer.txt", "ANSWER.txt", "ascii.txt", "askubuntu-profile.txt", "AskUbuntu_Translators.txt", "a.txt", "bash_result.txt", ...], [/* 80 vars */]) = 0
+++ exited with 0 +++
Run Code Online (Sandbox Code Playgroud)

shell 扩展*.txt为当前目录中以.txt扩展名结尾的所有文件名。如此有效地,您的 shell 将grep 'abc' *.txt命令转换为grep 'abc' file1.txt file2.txt file3.txt . . .. 因此,您的第二个假设是正确的。

第一个假设是不正确的 - 程序无法检测 glob。可以将*字符串参数作为字符串参数传递给命令,但是命令的工作是决定如何处理它。但是,正如我已经提到的,文件名扩展是您各自的 shell 的属性。

但是,这只有效,因为 grep 最后接受多个文件。另一个命令可能只允许传入 1 个文件。

非常正确 !程序不限制可接受的命令行参数的数量(例如,在 C 中是字符串数组const char *args[]和在 python 中sys.argv[]),但它们可以检测该数组的长度,或者是否有意外的东西位于错误的数组位置。 grep不这样做,并接受多个文件,这是设计使然。


另一方面,不正确的引用加上使用 grep 进行通配有时会成为一个问题。考虑一下:

bash-4.3$ echo "one two" | strace -e trace=execve grep *est*
execve("/bin/grep", ["grep", "self_test.sh", "test.wxg"], [/* 80 vars */]) = 0
+++ exited with 1 +++
Run Code Online (Sandbox Code Playgroud)

毫无准备的用户会期望 grep 将匹配est来自管道的任何带有字母的行,但 shell 的文件名扩展却扭曲了一切。我已经看到这种情况经常发生在这样做的人身上ps aux | grep shell_script_name.sh,他们希望发现他们的进程正在运行,但是因为他们从脚本所在的同一目录运行命令,shell 的文件名扩展使得grep 命令在幕后看起来与用户期望的完全不同.

正确的方法是使用单引号:

bash-4.3$ echo "one two" | strace -e trace=execve grep '*est*'
execve("/bin/grep", ["grep", "*est*"], [/* 80 vars */]) = 0
+++ exited with 1 +++
Run Code Online (Sandbox Code Playgroud)