当一个类似 Bash 的脚本作为没有 shebang 的二进制文件执行时,如何以及谁决定执行什么?
我猜使用 shebang 运行普通脚本是通过binfmt_script Linux 模块处理的,该模块检查 shebang,解析命令行并运行指定的脚本解释器。
但是,当有人在没有 shebang 的情况下运行脚本时会发生什么?我已经测试了直接execv
方法,发现那里没有内核魔术 - 即这样的文件:
$ cat target-script
echo Hello
echo "bash: $BASH_VERSION"
echo "zsh: $ZSH_VERSION"
运行仅执行execv
调用的已编译 C 程序会产生:
$ cat test-runner.cvoid main(( { if (execv("./target-script", 0( == -1( 错误((;}$ ./测试运行程序./target-script:执行格式错误
但是,如果我从另一个 shell 脚本执行相同的操作,它会使用与原始脚本相同的 shell 解释器运行目标脚本:
$ cat test-runner.bash#!/bin/bash./target-script$ ./test-runner.bash你好bash:4.1.0(1(发布中尚:
如果我对其他 shell 做同样的技巧(例如,Debian 的默认sh
- /bin/dash
(,它也可以工作:
$ cat test-runner.dash #!/bin/dash./target-script$ ./test-runner.dash你好砰:中尚:
神秘的是,它不能完全按照 zsh 的预期工作,并且不遵循一般方案。看起来 zsh 毕竟在此类文件上执行了/bin/sh
:
greycat@burrow-debian ~/z/test-runner $ cat test-runner.zsh#!/bin/zshecho ZSH_VERSION=$ZSH_VERSION./target-scriptgreycat@burrow-debian ~/z/test-runner $ ./test-runner.zshZSH_VERSION=4.3.10你好砰:中尚:
请注意,父脚本中的ZSH_VERSION
有效,而子脚本中的ZSH_VERSION
不起作用!
外壳(Bash,破折号(如何确定在没有shebang时执行的内容?我试图在 Bash/dash 源中挖掘那个地方,但是,唉,看起来我有点迷失在那里。谁能阐明确定没有 shebang 的目标文件应该作为脚本还是作为 Bash/dash 中的二进制文件执行的魔法?或者可能与内核/libc有某种交互,然后我欢迎解释它如何在Linux和FreeBSD内核/libcs中工作?
由于这发生在破折号中,而破折号更简单,所以我先看了看那里。
似乎exec.c是要查找的地方,并且相关的函数是tryexec
的,它是从shellexec
调用的,每当需要执行shell命令时都会调用。tryexec 函数的(简化版本(如下:
STATIC void
tryexec(char *cmd, char **argv, char **envp)
{
char *const path_bshell = _PATH_BSHELL;
repeat:
execve(cmd, argv, envp);
if (cmd != path_bshell && errno == ENOEXEC) {
*argv-- = cmd;
*argv = cmd = path_bshell;
goto repeat;
}
}
因此,如果发生ENOEXEC
,它总是将要执行的命令替换为自身的路径(_PATH_BSHELL
默认为 "/bin/sh"
(。这里真的没有魔法。
我发现 FreeBSD 在 bash
和它自己的sh
中表现出相同的行为。
bash
处理此问题的方式类似,但要复杂得多。如果你想进一步研究它,我建议阅读 bash 的execute_command.c
并专门查看 execute_shell_script
然后shell_execve
.评论非常具有描述性。
(看起来 Sorpigal 已经涵盖了它,但我已经输入了这个,它可能很有趣。
根据 Unix FAQ 的第 3.16 节,shell 首先查看幻数(文件的前两个字节(。一些数字表示二进制可执行文件; #!
表示该行的其余部分应解释为shebang。否则,shell 会尝试将其作为 shell 脚本运行。
此外,似乎csh
查看第一个字节,如果它是#
,它将尝试将其作为csh
脚本运行。