在 Linux 和 BSD 中使用和不使用 shebang 执行 Bash 脚本

Posted

技术标签:

【中文标题】在 Linux 和 BSD 中使用和不使用 shebang 执行 Bash 脚本【英文标题】:Bash script execution with and without shebang in Linux and BSD 【发布时间】:2011-11-08 06:23:57 【问题描述】:

当类似 Bash 的脚本在没有 shebang 的情况下作为二进制文件执行时,如何以及由谁来确定执行什么?

我猜想运行一个普通的脚本with 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.c 无效的主要() if (execv("./target-script", 0) == -1) 错误(); $ ./test-runner ./target-script: 执行格式错误

但是,如果我从另一个 shell 脚本执行相同的操作,它会使用与原始脚本相同的 shell 解释器运行目标脚本:

$ cat test-runner.bash #!/bin/bash ./目标脚本 $ ./test-runner.bash 你好 bash:4.1.0(1)-发布 zsh:

如果我对其他 shell 执行相同的技巧(例如,Debian 的默认 sh - /bin/dash),它也可以工作:

$ cat test-runner.dash #!/bin/破折号 ./目标脚本 $ ./test-runner.dash 你好 重击: zsh:

奇怪的是,它与 zsh 并没有像预期的那样工作,并且不遵循一般方案。看起来 zsh 毕竟在这些文件上执行了/bin/sh

graycat@burrow-debian ~/z/test-runner $ cat test-runner.zsh #!/bin/zsh 回声 ZSH_VERSION=$ZSH_VERSION ./目标脚本 graycat@burrow-debian ~/z/test-runner $ ./test-runner.zsh ZSH_VERSION=4.3.10 你好 重击: zsh:

请注意,父脚本中的 ZSH_VERSION 有效,而子脚本中的 ZSH_VERSION 无效!

shell(Bash、dash)如何确定在没有 shebang 时执行的内容?我试图在 Bash/dash 源中挖掘那个地方,但是,唉,看起来我有点迷失在那里。任何人都可以阐明确定没有 shebang 的目标文件应该作为脚本还是作为 Bash/dash 中的二进制文件执行的魔法?或者可能存在 与内核/libc 的某种交互,然后我欢迎解释它如何在 Linux 和 FreeBSD 内核/libcs​​ 中工作?

【问题讨论】:

这是一个关于 shell 内部结构的好问题,值得找出答案。但是,我要对所有读者说:实际上,不要。使用shebang。 直接使用不同的 shell 运行 target-script 可能更简单(例如 bash target-scriptdash target-script),而不是为每个 shell 创建一个测试运行程序。这应该给出相同的结果。 我测试了直接 execv 方法,发现其中没有内核魔法。好吧,您的 target-script 没有 shebang 行,那么您在这里期望什么内核魔法?如果你想测试内核魔法,那么你应该在你的脚本中加入 shebang 行(只是为了这个测试)。 我更喜欢这里的问题/答案:***.com/q/12296308/52074,因为链接的问题对问题/答案都有更多的赞成票,而且 IMO 的写作都更好。 【参考方案1】:

(看起来 Sorpigal 已经涵盖了它,但我已经输入了这个,它可能很有趣。)

根据Section 3.16 of the Unix FAQ,shell 首先查看幻数(文件的前两个字节)。一些数字表示二进制可执行文件; #! 表示该行的其余部分应解释为 shebang。否则,shell 会尝试将其作为 shell 脚本运行。

另外,csh 似乎在查看第一个字节,如果是#,它会尝试将其作为csh 脚本运行。

【讨论】:

shell 首先查看幻数(文件的前两个字节) 不,它不符合您提供链接的常见问题解答条目。 Shell 首先尝试将脚本作为二进制文件执行,然后由内核来查看幻数。只有在脚本以这种方式执行失败后,shell 才会尝试将其作为脚本运行 - 如果 execl() 成功启动程序,则永远不会执行 execl() 之外的代码。跨度> @PiotrDobrogost:你说得有道理。我在那里有点粗心。但是条目的其余部分表明它比这更复杂。 csh 确实查看第一个字节(不是两个字节)。另外值得注意的是,该条目与第一个 Linux 内核一样古老,因此情况可能已经发生了变化。【参考方案2】:

由于这发生在 dash 并且 dash 更简单,所以我先看那里。

好像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_scriptshell_execve。 cmets 的描述性很强。

【讨论】:

谢谢!我应该注意到 execute_shell_script 中的 cmets 有点误导:没有真正的“执行模式”位检查,这一切都依赖于 execve() 返回值,更复杂的是,bash 还试图自己模拟 shebang 解析,即它不只是运行它作为脚本获得的任何东西,而是尝试查找并运行解释器,如 shebang 中所指定的那样。这个功能在 Linux 中有点用处,因为binfmt_script 已经在内核级别处理它,但它可能对其他一些操作系统有帮助?.. @GreyCat:至少我希望它在 Windows 端口中有用,所以是的。 @GreyCat 没有真正的“执行模式”位检查,这一切都依赖于execve()返回值 根据execve(3)手册页,exec函数应如果 拒绝新进程映像文件的路径前缀中列出的目录的搜索权限,或新进程映像文件拒绝执行权限,或新进程映像文件,则失败并出现 EACCESS 错误不是常规文件,并且实现不支持执行其类型的文件。 由于 执行权限 取决于设置的执行位,人们可能会说正在检查该位,尽管不是直接检查: )

以上是关于在 Linux 和 BSD 中使用和不使用 shebang 执行 Bash 脚本的主要内容,如果未能解决你的问题,请参考以下文章

sed 就地标志,适用于 Mac (BSD) 和 Linux

资料收集:学习 Linux/*BSD/Unix 的 30 个最佳在线文档

Linux终端上的行编辑器 ed

Linux操作系统的发展与演变

学习 Linux/*BSD/Unix 的 30 个最佳在线文档 | Linux 中国

linux历史涉及知识点