带有 env shebang 的脚本在 Linux 上挂起

Rob*_*obo 8 linux

我们有一个看起来像这样的脚本:

#!/usr/bin/env node --unhandled-rejections=strict
console.log("Hi!");
Run Code Online (Sandbox Code Playgroud)

这在我的笔记本电脑 (OSX) 上运行良好,但当我们在 Linux 上运行它时,它就挂起了。使用 strace 我们可以看到它不断加载 libc 并执行

execve("./foo.sh", ["./foo.sh"], ["YARN_VERSION=1.22.4", "HOSTNAME=307d861c7c1a", "PWD=/", "HOME=/root", "NODE_VERSION=12.18.1", "TERM=xterm", "SHLVL=1", "PATH=/usr/local/sbin:/usr/local/"..., "_=/usr/bin/strace", "node --unhandled-rejections=stri"...]) = 0
Run Code Online (Sandbox Code Playgroud)

我们可以在这里看到 env 的参数被解释为环境变量。我们还知道“node --unhandled-rejections=strict”作为单个参数传入。

我们可以通过创建两个脚本来看到 OSX 中的差异。b1.sh:

args=("$@")
echo \"${args[0]}\" \"${args[1]}\" \"${args[2]}\"
Run Code Online (Sandbox Code Playgroud)

和 b2.sh

#!/usr/bin/env /tmp/b1.sh foo=bar
Run Code Online (Sandbox Code Playgroud)

当我们在 OSX 上运行 b2.sh 时,我们得到

"foo=bar" "./b2.sh" ""
Run Code Online (Sandbox Code Playgroud)

在 Linux 上它也会挂起。

很明显,在 Linux 和 OSX 中,传递给 env 的参数是不同的。在 OSX 中,“/tmp/b1.sh”和“foo=bar”是单独的参数。在 Linux 中,它们作为相同的参数传递。

但是为什么这会导致 env 一遍又一遍地执行相同的代码呢?

Phi*_*ppe 6

您可以使用-Sofenv来拆分node --unhandled-rejections=strict

~/tmp$ uname -a
Linux ubuntu 5.4.0-37-generic #41-Ubuntu SMP Wed Jun 3 18:57:02 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
~/tmp$ cat ./test.sh
#!/usr/bin/env -S node --unhandled-rejections=strict
console.log("Hi!");
~/tmp$ ./test.sh
Hi!
~/tmp$
Run Code Online (Sandbox Code Playgroud)

现在解释一下您帖子中发生的事情。

当您运行时./foo.sh,相当于运行以下命令:

/usr/bin/env "node --unhandled-rejections=strict" ./foo.sh
Run Code Online (Sandbox Code Playgroud)

这是摘录自man env

env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
Run Code Online (Sandbox Code Playgroud)

因此,env由于等号 (=),将“node --unhandled-rejections=strict”视为 NAME=VALUE"node --unhandled-rejections=strict"

然后env尝试./foo.sh作为命令运行,现在进入无限循环。


Bas*_*tch -1

阅读exec(3)execve(2)syscalls(2)env(1)strace(1)的文档;另请参见凭证(7)path_resolution(7)

hash-bang 行只能包含一行和一个空格。但最近的内核(例如 Debian/Sid 的 Linux 5.6)可能更加灵活。

你可能想开始你的脚本

#!/usr/bin/node --unhandled-rejections=strict
Run Code Online (Sandbox Code Playgroud)

另请阅读高级 Linux 编程系统调用(2)

“为什么它会一遍又一遍地循环调用 execve”

Linux内核开源的(实际上是免费软件,GPLv2许可)。

您可以下载其源代码并研究,和/或在kernelnewbies.org上询问,甚至可以修补您的内核源代码以适应您的需要。

我的观点是,修补内核源代码并不是一个好主意。更好的方法可能是根据您的需要编写自己的 C 程序调用,并使用GCCexecve等编译您的 C 代码(可能通过您的GCC 插件进行增强;然后另请阅读此草稿报告)。请务必阅读有关GCC 调用的内容

当我们在 OSX 上运行 b2.sh 时,我们得到

Linux不是OSX。您可能想了解有关POSIX规范的更多信息并阅读其中的一些(并查看这些幻灯片)

您可以用 C 语言编写自己的 Linux内核模块,提供系统调用的变体execve,但我不建议这样做。更喜欢在用户态编码,而不是在内核态。您的 C 程序和用户空间 可执行文件可能使用dlopen(3)dlsym(3)来加载插件