小心翼翼地模仿 Argv[0] 与 bash



我正在尝试编写一个非常仔细地模仿argv[0]/$0值的bash包装器脚本。 我正在使用 exec -a 来执行一个带有包装器的 argv[0] 值的单独程序。 我发现有时 bash 的 0 美元并不能提供我在 C 程序的 argv[0] 中获得的相同值。 下面是一个简单的测试程序,演示了 C 和 bash 的区别:

int main(int argc, char* argv[0])
{
printf("Argv[0]=%sn", argv[0]);
return 0;
}

#!/bin/bash 
echo $0=$0

当使用二进制文件的完整(绝对或相对)路径运行这些程序时,它们的行为相同:

$ /path/to/printargv
Argv[0]=/path/to/printargv
$ /path/to/printargv.sh 
$0=/path/to/printargv.sh
$ to/printargv
Argv[0]=to/printargv
$ to/printargv.sh 
$0=to/printargv.sh

但是当调用它们时,就好像它们在路径中一样,我得到不同的结果:

$ printargv
Arv[0]=printargv
$ printargv.sh 
$0=/path/to/printargv.sh

两个问题:

1)这是可以解释的预期行为,还是一个错误? 2)实现精心模仿argv[0]的目标的"正确"方法是什么?

编辑:错别字。

你在这里看到的是记录在案的bashexecve的行为(至少,它记录在 Linux 和 FreeBSD 上;我认为其他系统也有类似的文档),并反映了argv[0]构建的不同方式。

Bash(与任何其他 shell 一样)在执行各种扩展后从提供的命令行构造argv,根据需要重新拆分单词等。最终结果是,当您键入时

printargv

argv构造为{ "printargv", NULL }和键入时

to/printargv

argv构造为{ "to/printargv", NULL }.所以没有惊喜。

(在这两种情况下,如果有命令行参数,它们就会出现在从位置 1 开始的argv中。

但是在这一点上,执行路径会有所不同。当命令行中的第一个单词包含/时,它被视为文件名,无论是相对文件名还是绝对文件名。外壳不进行进一步处理;它只是使用提供的文件名作为其filename参数调用execve,并将先前构造的argv数组作为其argv参数。在这种情况下,argv[0]精确对应于filename

但是当命令没有斜杠时:

printargv

外壳做了更多的工作:

  • 首先,它检查名称是否是用户定义的 shell 函数。如果是这样,它将执行它,并从已经构造的argv数组中获取$1...$n。(不过,$0继续从脚本调用中argv[0]

  • 然后,它会检查名称是否为内置 bash 命令。如果是这样,它将执行它。内置如何与命令行参数交互超出了此答案的范围,并且不是真正的用户可见的。

  • 最后,它尝试通过搜索$PATH的组件并查找可执行文件来查找与命令对应的外部实用程序。如果找到一个,它会调用execve,给它找到的路径作为filename参数,但仍使用由命令中的单词组成的argv数组。所以在这种情况下,filenameargv[0]对应。

因此,在这两种情况下,shell 最终都会调用execve,提供一个文件路径(可能是相对的)作为filename参数,并提供单词拆分命令作为argv参数。

如果指示的文件是可执行映像,则真的没有什么可说的了。图像被加载到内存中,并使用提供的argv向量调用其mainargv[0]将是单个单词或相对或绝对路径,仅取决于最初键入的内容。

但是,如果指示的文件是脚本,则加载器将产生错误,execve将检查文件是否以 shebang (#!) 开头。 (从 Posix 2008 开始,execve还将尝试使用系统 shell 将文件作为脚本运行,就好像它#!/bin/sh为一个 shebang 行一样。

以下是 Linux 上execve的文档:

解释器脚本是启用了执行权限的文本文件,其第一行的格式为:

#! interpreter [optional-arg]

解释器必须是可执行文件的有效路径名。 如果 execve() 的文件名参数指定了解释器脚本,则解释器将使用以下参数调用:

interpreter [optional-arg] filename arg...

其中arg...execve()argv论点所指向的一系列词,从argv[1]开始。

请注意,在上面,filename参数是要execvefilename参数。鉴于舍邦线#!/bin/bash我们现在有

/bin/bash to/printargv           # If the original invocation was to/printargv

/bin/bash /path/to/printargv     # If the original invocation was printargv

请注意,argv[0]实际上已经消失了。

然后bash运行文件中的脚本。在执行脚本之前,它会$0设置为给定的 filename 参数(在我们的示例中为to/printargv/path/to/printargv),并将$1...$n设置为其余参数,这些参数是从原始命令行中的命令行参数复制的。

总之,如果使用不带斜杠的文件名调用命令:

  • 如果文件名包含可执行映像,它将看到argv[0]作为键入的命令名称。

  • 如果文件名包含带有 shebang 行的 bash 脚本,则脚本将$0视为脚本文件的实际路径。

如果您使用带斜杠的文件名调用该命令,在这两种情况下,它都会看到 argv[0] 作为键入的文件名(这可能是相对的,但显然总是有一个斜杠)。

另一方面,如果通过显式调用 shell 解释器来调用脚本 (bash printargv),脚本将$0视为键入的文件名,这不仅可能是相对的,而且可能没有斜杠。

所有这些都意味着,只有当你知道你想要模仿的脚本调用形式时,你才能"仔细模仿argv[0]"。(这也意味着脚本永远不应该依赖于argv[0]的值,但这是一个不同的主题。

如果这样做是为了单元测试,则应提供一个选项来指定要作为 argv[0] 提供的值。许多试图分析的 shell 脚本$0假定它是一个文件路径。他们不应该这样做,因为它可能不是,但它就在那里。如果你想把这些公用设施抽出来,你需要提供一些垃圾价值作为$0。否则,作为默认值的最佳选择是提供脚本文件的路径。

相关内容

  • 没有找到相关文章

最新更新