根据我的理解,shell 会解释 glob 通配符,然后对每个匹配的文件名运行给定的命令。假设我abc1, abc2, and abc3
当前目录中有文件:。然后,例如,echo abc*
将对以“abc”开头的每个文件名回显一次。
但是,如果我运行grep 'foo' abc*
,我想这应该运行:
grep 'foo' abc1
grep 'foo' abc2
grep 'foo' abc3
这意味着我应该得到以下输出(假设所有文件都包含一行“foo”):
foo
foo
foo
然而,我得到的却是:
abc1:foo
abc2:foo
abc3:foo
所以我认为这有两种可能的解释。首先,grep 可以以某种方式检测到它与 glob 表达式一起使用,并通过在匹配之前输出文件名来响应。其次,由于您可以将多个文件传递给 grep,因此 shell 实际上只运行 1 个命令:
grep 'foo' abc1 abc2 abc3
但是,这只能在 grep 末尾接受多个文件的情况下才有效。另一个命令可能只允许传入 1 个文件。因此,如果您想对与 glob 匹配的多个文件运行该命令,如果通过上述第二种方法进行 glob 操作,它将不起作用。
无论如何,有人可以解释一下这个问题吗?
谢谢!
答案1
这就是诀窍:命令不知道,这是 shell 完成的工作
例如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 +++
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 +++
毫无准备的用户会认为 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 +++