用bash小心模仿Argv [0]

我正在尝试编写一个bash包装器脚本,它非常仔细地模仿argv [0] / $0的值.我正在使用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]的目标的“正确”方法是什么?

编辑:错别字.

最佳答案 你在这里看到的是
bash和execve的记录行为(至少,它记录在
Linux
FreeBSD;我认为其他系统有类似的文档),并反映了构造argv [0]的不同方式.

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

printargv

当你输入时,argv被构造为{“printargv”,NULL}

to/printargv

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

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

但是执行路径在那时出现了分歧.当命令行中的第一个单词包含/时,它被认为是文件名,无论是相对还是绝对.外壳没有进一步处理;它只是使用提供的文件名作为其文件名参数调用execve,而先前构造的argv数组作为其argv参数.在这种情况下,argv [0]精确对应于文件名

但是当命令没有斜线时:

printargv

shell做了很多工作:

>首先,它检查名称是否是用户定义的shell函数.如果是这样,它执行它,从已构造的argv数组中取出$1 … $n. (但是,从脚本调用开始,$0仍然是argv [0].)
>然后,它检查名称是否是内置bash命令.如果是这样,它会执行它.内置函数如何与命令行参数交互超出了这个答案的范围,并且实际上并不是用户可见的.
>最后,它尝试通过搜索$PATH的组件并查找可执行文件来查找与该命令对应的外部实用程序.如果找到一个,它会调用execve,为它提供它作为filename参数找到的路径,但仍然使用由命令中的单词组成的argv数组.所以在这种情况下,filename和argv [0]不对应.

因此,在这两种情况下,shell最终调用execve,提供文件路径(可能是相对的)作为filename参数,word-split命令作为argv参数.

如果指示的文件是可执行图像,那么没有什么可说的了.图像被加载到内存中,并使用提供的argv向量调用其主要内容. 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:

06003

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:

06004

where arg... is the series of words pointed to by the argv argument of execve(), starting at argv[1].

请注意,在上面,filename参数是execve的filename参数.鉴于shebang line#!/ 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设置为它给出的文件名参数,在我们的示例中为/ printargv或/ path / to / printargv,并将$1 … $n设置为剩余的参数,这些参数是从原始命令行中的命令行参数.

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

>如果文件名包含可执行映像,则会将argv [0]视为键入的命令名称.
>如果文件名包含带有shebang行的bash脚本,则脚本将看到$0作为脚本文件的实际路径.

如果使用带有斜杠的文件名调用该命令,则在两种情况下都会将argv [0]视为文件名为typed(可能是相对的,但显然总是有斜杠).

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

所有这些意味着,如果你知道调用你希望模仿的脚本的形式,你只能“小心地模仿argv [0]”. (这也意味着脚本永远不应该依赖于argv [0]的值,但这是一个不同的主题.)

如果您正在进行单元测试,则应提供一个选项来指定要提供的值为argv [0].尝试分析$0的许多shell脚本都假定它是一个文件路径.他们不应该这样做,因为它可能不是,但它确实如此.如果你想抽出那些实用程序,你需要提供一些垃圾值为$0.否则,默认情况下,最好的办法是提供脚本文件的路径.

点赞