这里展示的是
bash
和
execve
的文档化行为(至少在
Linux 和
FreeBSD 上有文档记录;我想其他系统也有类似的文档),并反映了构建
argv[0]
的不同方式。
Bash(像任何其他 shell 一样)从提供的命令行构建
argv
,在执行各种扩展、必要时重新分割单词等操作后。最终结果是当你输入
printargv
"argv"被构造为
{"printargv", NULL}
,当你输入时。
to/printargv
argv
被构建为{"to/printargv", NULL}
。所以没有什么意外的。
(在两种情况下,如果有命令行参数,它们将从位置1开始出现在argv
中。)
但是在那一点上,执行路径分叉。当命令行中的第一个单词包含/时,则被认为是文件名,可以是相对或绝对路径。Shell不进行进一步处理;它只是使用提供的文件名作为其filename
参数和先前构建的argv
数组作为其argv
参数调用execve
。在这种情况下,argv[0]
恰好对应于filename
。
但是当命令没有斜杠时:
printargv
外壳程序要做更多的工作:
首先,它检查名称是否是用户定义的外壳函数。如果是,它将执行该函数,并使用已构建的 argv
数组中的 $1...$n
。(尽管如此,$0
仍然是来自脚本调用的 argv[0]
。)
然后,它会检查名称是否为内置的 bash 命令。如果是,它将执行该命令。内置命令如何与命令行参数交互不在本答案的范围之内,并且并不真正可见。
最后,它尝试找到与命令对应的外部实用程序,通过搜索 $PATH
的组件并查找可执行文件。如果找到一个,则调用 execve
,并将找到的路径作为 filename
参数传递,但仍然使用由命令单词组成的 argv
数组。因此,在这种情况下,filename
和 argv[0]
不匹配。
因此,在这两种情况下,shell 最终都会调用
execve
函数,将文件路径(可能是相对路径)作为
filename
参数,将经过单词分割的命令作为
argv
参数。
如果指定的文件是可执行映像,则实际上没有更多要说的了。该映像被加载到内存中,并使用提供的
argv
向量调用其
main
函数。
argv[0]
将是一个单词或相对或绝对路径,这仅取决于最初键入的内容。
但是,如果指定的文件是脚本,则加载器将产生错误,
execve
将检查文件是否以 shebang(
#!
)开头。(自 Posix 2008 起,
execve
还会尝试使用系统 shell 运行文件作为脚本,就好像它具有
#!/bin/sh
作为 shebang 行一样。)
以下是 Linux 上
execve
的文档:
An interpreter script is a text file that has execute permission enabled and whose first line is of the form:
The interpreter must be a valid pathname for an executable file. If the filename argument of execve() specifies an interpreter script, then interpreter will be invoked with the following arguments:
interpreter [optional-arg] filename arg...
where arg...
is the series of words pointed to by the argv
argument of execve()
, starting at argv[1]
.
请注意,上述中的
filename
参数是
execve
函数的
filename
参数。给定shebang行
#!/bin/bash
,我们现在有以下两种情况:
/bin/bash to/printargv
或者
/bin/bash /path/to/printargv
请注意,
argv[0]
已经消失了。
然后,
bash
在文件中运行脚本。在执行脚本之前,它将
$0
设置为给定的文件名参数,在我们的示例中为
to/printargv
或
/path/to/printargv
,并将
$1...$n
设置为剩余的参数,这些参数是从原始命令行中的命令行参数复制而来的。
总之,如果您使用不带斜杠的文件名调用命令:
如果您使用带斜杠的文件名调用命令,则在两种情况下它都会将 argv [0] 视为键入的文件名(可能是相对的,但显然总是有一个斜杠)。
另一方面,如果您通过明确调用shell解释器(
bash printargv
)来调用脚本,则脚本将把
$0
视为键入的文件名,这不仅可能是相对路径,而且可能没有斜杠。
所有这些意味着,如果您知道要模拟的调用脚本的形式,那么您只能“小心地模仿argv [0]”。 (这也意味着脚本永远不应该依赖于
argv [0]
的值,但这是一个不同的话题。)
如果您正在进行单元测试,您应该提供一个选项来指定要提供给argv [0]的值。许多尝试分析
$0
的shell脚本都假定它是文件路径。他们不应该这样做,因为它可能不是,但事实就是如此。如果您想排除这些实用程序,您需要提供一些垃圾值作为
$0
。否则,您的最佳选择是提供脚本文件的路径作为默认值。