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

Gre*_*Cat 17 linux bash shell freebsd dash-shell

当一个类似Bash的脚本作为没有shebang的二进制文件执行时,如何以及由谁决定执行什么?

我想 shebang 运行一个普通的脚本是用binfmt_script Linux模块处理的,它检查一个shebang,parses命令行并运行指定的脚本解释器.

但是当有人在没有shebang的情况下运行脚本时会发生什么?我已经测试了直接execv方法并发现那里没有内核魔法 - 即像这样的文件:

$ cat target-script
echo Hello
echo "bash: $BASH_VERSION"
echo "zsh: $ZSH_VERSION"
Run Code Online (Sandbox Code Playgroud)

运行仅执行execv调用的已编译C程序会产生:

$ cat test-runner.c
void main() {
        if (execv("./target-script", 0) == -1)
                perror();
}
$ ./test-runner
./target-script: Exec format error

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

$ cat test-runner.bash
#!/bin/bash
./target-script

$ ./test-runner.bash 
Hello
bash: 4.1.0(1)-release
zsh: 

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

$ cat test-runner.dash   
#!/bin/dash
./target-script

$ ./test-runner.dash 
Hello
bash: 
zsh: 

神秘的是,它与zsh没有达到预期的效果,并且不遵循一般方案./bin/sh毕竟看起来像zsh 在这些文件上执行:

greycat@burrow-debian ~/z/test-runner $ cat test-runner.zsh 
#!/bin/zsh
echo ZSH_VERSION=$ZSH_VERSION
./target-script

greycat@burrow-debian ~/z/test-runner $ ./test-runner.zsh 
ZSH_VERSION=4.3.10
Hello
bash: 
zsh: 

请注意,ZSH_VERSION在父脚本工作,而ZSH_VERSION在孩子没有!

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

Sor*_*gal 16

由于这种情况发生在破折号和破折号更简单,我先看了一下.

看起来像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;
        }
}
Run Code Online (Sandbox Code Playgroud)

因此,它只是总是替换命令以执行自身的路径(_PATH_BSHELL默认为"/bin/sh"),如果ENOEXEC发生.这里真的没有魔力.

我发现FreeBSD bash在其自身中表现出相同的行为sh.

bash处理这个的方式类似但更复杂.如果你想进一步研究它,我建议你阅读bash ,然后execute_command.c专门看一下.评论非常具有描述性.execute_shell_scriptshell_execve

  • 谢谢!我应该注意`execute_shell_script`中的注释有点误导:没有真正的"执行模式"位检查,它都依赖于`execve()`返回值,而且更复杂的是,bash也试图模仿shebang解析,即它不仅仅运行它作为脚本获得的任何东西,而是试图找到并运行一个解释器,如shebang中所指定的.这个功能在Linux中有点无用,因为`binfmt_script`已经在内核级别处理它,但它可能对其他一些操作系统有用吗? (2认同)
  • @GreyCat*没有真正的"执行模式"位检查,它全部依赖于`execve()`返回值*根据execve(3)手册页,如果*搜索权限是exec函数将失败并出现`EACCESS`错误拒绝新进程映像文件的路径前缀中列出的目录,**或新进程映像文件拒绝执行权限**,或者新进程映像文件不是常规文件,并且实现不支持执行其文件类型.*因为*执行权限*取决于执行位设置可能会说这个位正在被检查,虽然不是直接:) (2认同)

Tom*_*ych 9

(看起来Sorpigal已经覆盖了它,但我已经输入了它,它可能是有意义的.)

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

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