用bash仔细模仿Argv [0]

Jas*_*ser 6 bash wrapper

我正在尝试编写一个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;
}
Run Code Online (Sandbox Code Playgroud)

#!/bin/bash 
echo \$0=$0
Run Code Online (Sandbox Code Playgroud)

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

$ /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
Run Code Online (Sandbox Code Playgroud)

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

$ printargv
Arv[0]=printargv

$ printargv.sh 
$0=/path/to/printargv.sh
Run Code Online (Sandbox Code Playgroud)

两个问题:

1)这是可以解释的预期行为,还是错误?2)实现仔细模仿argv [0]的目标的“正确”方法是什么?

编辑:错别字。

ric*_*ici 2

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

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

printargv
Run Code Online (Sandbox Code Playgroud)

argv{ "printargv", NULL }当您键入时构建

to/printargv
Run Code Online (Sandbox Code Playgroud)

argv被构造为{ "to/printargv", NULL }. 所以这并不奇怪。

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

但此时执行路径有所不同。当命令行中的第一个单词包含 / 时,它被视为文件名,无论是相对文件名还是绝对文件名。外壳不做进一步加工;它只是execve使用提供的文件名作为其filename参数以及argv之前构造的数组作为其argv参数进行调用。在这种情况下,argv[0]正好对应于filename

但是当命令没有斜杠时:

printargv
Run Code Online (Sandbox Code Playgroud)

shell 做了更多的工作:

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

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

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

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

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

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

execve这是Linux 上的文档:

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

      #! interpreter [optional-arg]
Run Code Online (Sandbox Code Playgroud)

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

      interpreter [optional-arg] filename arg...
Run Code Online (Sandbox Code Playgroud)

其中是的参数arg... 所指向的一系列单词,从 开始。argvexecve()argv[1]

请注意,在上面,filename参数是filename的参数execve。鉴于 shebang 线,#!/bin/bash我们现在有

/bin/bash to/printargv           # If the original invocation was to/printargv
Run Code Online (Sandbox Code Playgroud)

或者

/bin/bash /path/to/printargv     # If the original invocation was printargv
Run Code Online (Sandbox Code Playgroud)

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

bash然后运行文件中的脚本。在执行脚本之前,它设置$0为给定的文件名参数(在我们的示例中为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. 否则,默认情况下最好的选择是提供脚本文件的路径。