用bash小心模仿Argv [0]

时间:2016-05-21 23:21:11

标签: bash wrapper

我试图编写一个非常小心地模仿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]的目标?

编辑:错别字。

1 个答案:

答案 0 :(得分:2)

您在此处看到的是bashexecve的记录行为(至少,它记录在LinuxFreeBSD上;我认为其他系统有类似的文档),并反映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数组。因此,在这种情况下,filenameargv[0] 对应。

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

如果指示的文件是可执行图像,那么真的没有什么可说的了。图像被加载到内存中,并使用提供的main向量调用其argvargv[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]。否则,默认情况下,最好的办法是提供脚本文件的路径。