gpt4 book ai didi

bash - 用 bash 仔细模仿 Argv[0]

转载 作者:行者123 更新时间:2023-11-29 09:00:57 24 4
gpt4 key购买 nike

我正在尝试编写一个非常仔细地模仿 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]的目标的“正确”方法是什么?

编辑:错别字。

最佳答案

您在这里看到的是 bash 的记录行为和 execve (至少,它记录在 LinuxFreeBSD 上;我认为其他系统也有类似的文档),并反射(reflect)了 argv[0] 的不同方式被构造。
Bash(与任何其他 shell 一样)构造 argv从提供的命令行,在执行各种扩展后,根据需要重新拆分单词,等等。最终结果是当你输入

printargv
argv构造为 { "printargv", NULL }当你打字时
to/printargv
argv构造为 { "to/printargv", NULL } .所以没有惊喜。
(在这两种情况下,如果有命令行参数,它们就会出现在 argv 中,从位置 1 开始。)
但在这一点上执行路径出现分歧。当命令行中的第一个单词包含/时,它被认为是一个文件名,无论是相对的还是绝对的。 shell 不做进一步处理;它只是调用 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参数和分词命令为 argv争论。
    如果指示的文件是一个可执行镜像,那就没有什么可说的了,真的。图像被加载到内存中,其 main使用提供的 argv 调用向量。 argv[0]将是单个单词或相对或绝对路径,仅取决于最初键入的内容。
    但是如果指定的文件是一个脚本,加载器会产生一个错误和 execve将检查文件是否以shebang开头( #! )。 (自 Posix 2008 起, execve 也将尝试使用系统 shell 将文件作为脚本运行,就好像它有 #!/bin/sh 作为shebang 行一样。)
    这是 execve 的文档在 Linux 上:

    An interpreter script is a text file that has execute permission enabled and whose first line is of the form:

          #! interpreter [optional-arg]

    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:

          interpreter [optional-arg] filename arg...

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


    注意上面的 filename参数是 filename论据 execve .鉴于shebang线 #!/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 , 并设置 $1...$n到其余参数,这些参数是从原始命令行中的命令行参数复制而来的。
    总之,如果您使用不带斜杠的文件名调用命令:
  • 如果文件名包含可执行镜像,它将看到 argv[0]作为键入的命令名称。
  • 如果文件名包含带有 shebang 行的 bash 脚本,脚本将看到 $0作为脚本文件的实际路径。

  • 如果您使用带斜杠的文件名调用命令,在这两种情况下,它都会将 argv[0] 视为键入的文件名(可能是相对的,但显然总是有斜杠)。
    另一方面,如果您通过显式调用 shell 解释器来调用脚本( bash printargv ),脚本将看到 $0作为输入的文件名,它不仅可能是相对的,而且可能没有斜线。
    所有这一切都意味着,如果您知道调用要模仿的脚本的形式,则只能“仔细模仿 argv[0]”。 (这也意味着脚本永远不应该依赖于 argv[0] 的值,但这是一个不同的主题。)
    如果您这样做是为了单元测试,您应该提供一个选项来指定要提供什么值作为 argv[0]。许多试图分析的 shell 脚本 $0假设它是一个文件路径。他们不应该那样做,因为它可能不存在,但它确实存在。如果您想清除这些实用程序,您需要提供一些垃圾值作为 $0 .否则,默认情况下最好的选择是提供脚本文件的路径。

    关于bash - 用 bash 仔细模仿 Argv[0],我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37368987/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com