我试图编写一个非常小心地模仿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]=%s\n", 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]的目标?
编辑:错别字。
答案 0 :(得分:2)
您在此处看到的是bash
和execve
的记录行为(至少,它记录在Linux和FreeBSD上;我认为其他系统有类似的文档),并反映argv[0]
构建的不同方式。
Bash(与任何其他shell一样)在执行各种扩展后,从提供的命令行构造argv
,根据需要重新分配单词,等等。最终结果是当您键入
printargv
argv
构造为{ "printargv", NULL }
,当您输入
to/printargv
argv
构造为{ "to/printargv", NULL }
。所以没有惊喜。
(在这两种情况下,如果有命令行参数,它们将从位置1开始出现在argv
中。)
但是执行路径在那时出现了分歧。当命令行中的第一个单词包含/时,它被认为是文件名,无论是相对还是绝对。外壳没有进一步处理;它只是使用提供的文件名作为execve
参数调用filename
,并将先前构造的argv
数组作为其argv
参数。在这种情况下,argv[0]
与filename
但是当命令没有斜杠时:
printargv
shell做了很多工作:
首先,它检查名称是否是用户定义的shell函数。如果是这样,它将执行它,$1...$n
取自已构造的argv
数组。 ($0
继续从脚本调用argv[0]
开始。)
然后,它检查名称是否是内置bash命令。如果是这样,它会执行它。内置函数如何与命令行参数进行交互超出了这个答案的范围,并且实际上并不是用户可见的。
最后,它尝试通过搜索$PATH
的组件并查找可执行文件来查找与该命令对应的外部实用程序。如果找到一个,它会调用execve
,给它找到filename
参数的路径,但仍然使用由命令中的单词组成的argv
数组。因此,在这种情况下,filename
和argv[0]
不对应。
因此,在这两种情况下,shell最终调用execve
,提供文件路径(可能是相对的)作为filename
参数,word-split命令作为argv
参数。
如果指示的文件是可执行图像,那么真的没有什么可说的了。图像被加载到内存中,并使用提供的main
向量调用其argv
。 argv[0]
将是一个单词或相对或绝对路径,仅取决于最初输入的内容。
但是如果指示的文件是脚本,则加载器将产生错误,execve
将检查文件是否以shebang(#!
)开头。 (自Posix 2008起,execve
也将尝试使用系统shell将该文件作为脚本运行,就好像它有#!/bin/sh
作为shebang行一样。)
这是Linux上execve
的文档:
解释器脚本是一个文本文件,它启用了执行权限,其第一行的格式为:
#! interpreter [optional-arg]
解释器必须是可执行文件的有效路径名。如果execve()的filename参数指定了解释器脚本,则将使用以下参数调用解释器:
interpreter [optional-arg] filename arg...
其中
arg...
是argv
的{{1}}参数指向的一系列单词,从execve()
开始。
请注意,在上文中,argv[1]
参数是filename
的{{1}}参数。鉴于shebang行filename
,我们现在要么
execve
或
#!/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
,并将to/printargv
设置为其余参数,从原始命令行中的命令行参数复制。
总之,如果使用不带斜杠的文件名调用命令:
如果文件名包含可执行图像,则会将/path/to/printargv
视为键入的命令名称。
如果文件名包含带有shebang行的bash脚本,则脚本会将$1...$n
视为脚本文件的实际路径。
如果使用带有斜杠的文件名调用该命令,则在两种情况下都会将argv [0]视为文件名为typed(可能是相对的,但显然总是有斜杠)。
另一方面,如果通过显式调用shell解释器来调用脚本(argv[0]
),脚本将看到$0
作为键入的文件名,这不仅可能是相对的,而且也是可能没有斜线。
这意味着你只能小心地模仿argv [0]"如果你知道调用你希望模仿的脚本的形式。 (这也意味着脚本不应该依赖bash printargv
的值,但这是一个不同的主题。)
如果您正在进行单元测试,则应提供一个选项来指定要提供的值为argv [0]。尝试分析$0
的许多shell脚本都假定它是一个文件路径。他们不应该这样做,因为它可能不是,但它确实如此。如果您想要抽出这些实用程序,您将要提供一些垃圾值argv[0]
。否则,默认情况下,最好的办法是提供脚本文件的路径。