为啥`#!/usr/bin/env var=val command`会进入无限循环

Posted

技术标签:

【中文标题】为啥`#!/usr/bin/env var=val command`会进入无限循环【英文标题】:why do `#!/usr/bin/env var=val command` gets into an infinite loop为什么`#!/usr/bin/env var=val command`会进入无限循环 【发布时间】:2016-01-12 14:23:04 【问题描述】:

man(1) env 它说:

env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]

所以考虑print_A.sh

#!/usr/bin/env A=b bash
echo A is $A

当我使用./print_A.sh 运行它时,它会挂起。

使用strace ./print_A.sh 运行它,我得到以下日志,重复:

execve("/path/to/print_A.sh", ["/path/to/print_A.sh"...], [/* 114 vars */]) = 0
uname(sys="Linux", node="my-host", ...) = 0
brk(0)                                  = 0x504000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2a95556000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, st_mode=S_IFREG|0644, st_size=171528, ...) = 0
mmap(NULL, 171528, PROT_READ, MAP_PRIVATE, 3, 0) = 0x2a95557000
close(3)                                = 0
open("/lib64/tls/libc.so.6", O_RDONLY)  = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\305\30100\0\0\0"..., 832) = 832
fstat(3, st_mode=S_IFREG|0755, st_size=1641152, ...) = 0
mmap(0x3030c00000, 2330696, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3030c00000
mprotect(0x3030d30000, 1085512, PROT_NONE) = 0
mmap(0x3030e2f000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12f000) = 0x3030e2f000
mmap(0x3030e35000, 16456, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3030e35000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2a95581000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2a95582000
mprotect(0x3030e2f000, 16384, PROT_READ) = 0
mprotect(0x3030b14000, 4096, PROT_READ) = 0
arch_prctl(ARCH_SET_FS, 0x2a95581b00)   = 0
munmap(0x2a95557000, 171528)            = 0
brk(0)                                  = 0x504000
brk(0x525000)                           = 0x525000
open("/usr/lib/locale/locale-archive", O_RDONLY) = 3
fstat(3, st_mode=S_IFREG|0644, st_size=48529088, ...) = 0
mmap(NULL, 48529088, PROT_READ, MAP_PRIVATE, 3, 0) = 0x2a95583000
close(3)                                = 0

如下所述,在 hash-bang 中运行命令并不等同于在命令行上运行,但是,为什么它会进入无限循环?

【问题讨论】:

Hashbang 参数只分成两个词,因此这真的是env A='b python2.7'。然而,这是特定于系统的,并且过去的处理方式有所不同(我记得 FreeBSD 提交更改了这一点,here is the Linux discussion。您可以使用#! /usr/bin/env a=b 获得相同的结果,它也(至少在这里)永远循环。不是很确定,但为什么会这样。 另外:why does setting an initial environment using env stall the launch of my Python script on Ubuntu,然而,它并没有回答实际的问题为什么会发生这种情况。 【参考方案1】:

这个答案有两个部分。一个已经在duplicate question 中给出。然而,那里的答案解释了问题的根本原因,而不是实际发生的情况。

第 1 部分 - 是什么原因造成的?

Hashbang 解析从未真正标准化。 Here 是 Sven Mascheck 的一篇非常好的文章,其中还包括一个表格,其中包含不同操作系统的行为。

表格显示,例如Linux 将所有参数合二为一,这意味着#!/usr/bin/env A=b bash'A=b bash' 作为第一个参数来执行env

第 2 部分 -- 为什么是无限循环?

发生的情况是,env 被执行,它设置环境变量A='b bash' 然后重新执行原始脚本。这导致内核再次重新解释 hashbang,我们得到一个无限的env-exec 循环。

稍微想了想,问题就很明显了:

第一行 #!/bin/sh param 的文件 test.sh'/bin/sh' 'param' 'test.sh' 执行 /bin/sh。脚本名称作为新的命令行参数附加(即argv)。

因此在示例中,env 实际上被执行为/usr/bin/env 'A=b bash'script_name

env 就这样按照它的指示去做,设置变量,然后执行 script_name。这又开始了 hashbang 解释,我们得到了循环。

【讨论】:

【参考方案2】:

添加正在发生的事情的演示,以补充已接受的答案:

我们可以制作一个小可执行文件,它只显示调用它的参数,并将其放在 shebang 行中:

$ cat showargs.c

#include <stdio.h>
int main(int argc, char *argv[]) 
  int i;
  for (i = 0; i < argc; i++)
    printf("arg %d = '%s'\n", i, argv[i]);
  return 0;


$ gcc -Wall -o /tmp/showargs showargs.c

$ cat testscript
#!/tmp/showargs A=b bash
(rest of script itself ignored here)

$ chmod +x testscript 

$ ./testscript
arg 0 = '/tmp/showargs'
arg 1 = 'A=b bash'
arg 2 = './testscript'

注意这里的A=b bash 是一个命令行参数。

这与:

$ /tmp/showargs A=b bash ./testscript 
arg 0 = '/tmp/showargs'
arg 1 = 'A=b'
arg 2 = 'bash'
arg 3 = './testscript'

命令行被调用shell标记的地方。

因此用env 代替showargs,我们得到了接受答案中描述的无限循环(将A 设置为b bash 并再次重新调用相同的脚本)。

【讨论】:

以上是关于为啥`#!/usr/bin/env var=val command`会进入无限循环的主要内容,如果未能解决你的问题,请参考以下文章

php脚本#!/usr/bin/env php 写法

php脚本#!/usr/bin/env php写法的好处

#!/usr/bin/env python与#!/usr/bin/python的区别

关于#!/usr/bin/env python 的用法

#!/usr/bin/env python与#!/usr/bin/python的区别

#! /usr/bin node 和#! /usr/bin/env node两者的区别