在 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 程序会产生:
但是,如果我从另一个 shell 脚本执行相同的操作,它会使用与原始脚本相同的 shell 解释器运行目标脚本:
$ cat test-runner.bash #!/bin/bash ./目标脚本 $ ./test-runner.bash 你好 bash:4.1.0(1)-发布 zsh:如果我对其他 shell 执行相同的技巧(例如,Debian 的默认 sh
- /bin/dash
),它也可以工作:
奇怪的是,它与 zsh 并没有像预期的那样工作,并且不遵循一般方案。看起来 zsh 毕竟在这些文件上执行了/bin/sh
:
请注意,父脚本中的 ZSH_VERSION
有效,而子脚本中的 ZSH_VERSION
无效!
shell(Bash、dash)如何确定在没有 shebang 时执行的内容?我试图在 Bash/dash 源中挖掘那个地方,但是,唉,看起来我有点迷失在那里。任何人都可以阐明确定没有 shebang 的目标文件应该作为脚本还是作为 Bash/dash 中的二进制文件执行的魔法?或者可能存在 与内核/libc 的某种交互,然后我欢迎解释它如何在 Linux 和 FreeBSD 内核/libcs 中工作?
【问题讨论】:
这是一个关于 shell 内部结构的好问题,值得找出答案。但是,我要对所有读者说:实际上,不要。使用shebang。 直接使用不同的 shell 运行target-script
可能更简单(例如 bash target-script
或 dash 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_script
和 shell_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 个最佳在线文档