为什么 `#!/usr/bin/env var=val command` 进入无限循环
why do `#!/usr/bin/env var=val command` gets into an infinite loop
在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, "7ELF[=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=]>[=13=][=13=][=13=][=13=]05100[=13=][=13=][=13=]"..., 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 中执行命令并不等同于在命令行中执行命令 运行,但是,为什么它会进入无限循环?
这个答案有两个部分。已经在duplicate question中给出了一个。然而,那里的答案解释了问题的根本原因,而不是实际发生的事情。
第 1 部分 - 是什么原因造成的?
Hashbang 解析从未真正标准化。 Here 是 Sven Mascheck 写的一篇非常好的文章,其中还包括一个 table 以及针对不同操作系统的行为。
table 表明,例如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
作为 '/bin/sh' 'param' 'test.sh'
。脚本名称作为新的命令行参数附加(即附加到 argv
)。
因此在例子中,env
实际上执行为/usr/bin/env 'A=b bash'
script_name
.
env
这样就按照它说的去做,设置变量,然后执行 script_name
。这再次开始 hashbang 解释,我们得到了我们的循环。
添加正在发生的事情的演示,以补充已接受的答案:
我们可以制作一个小的可执行文件,它只显示调用它的参数,并将其放在 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
是单个 command-line 参数。
这对比于:
$ /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
和 re-invokes 同样的脚本)。
在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, "7ELF[=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=]>[=13=][=13=][=13=][=13=]05100[=13=][=13=][=13=]"..., 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 中执行命令并不等同于在命令行中执行命令 运行,但是,为什么它会进入无限循环?
这个答案有两个部分。已经在duplicate question中给出了一个。然而,那里的答案解释了问题的根本原因,而不是实际发生的事情。
第 1 部分 - 是什么原因造成的?
Hashbang 解析从未真正标准化。 Here 是 Sven Mascheck 写的一篇非常好的文章,其中还包括一个 table 以及针对不同操作系统的行为。
table 表明,例如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
作为 '/bin/sh' 'param' 'test.sh'
。脚本名称作为新的命令行参数附加(即附加到 argv
)。
因此在例子中,env
实际上执行为/usr/bin/env 'A=b bash'
script_name
.
env
这样就按照它说的去做,设置变量,然后执行 script_name
。这再次开始 hashbang 解释,我们得到了我们的循环。
添加正在发生的事情的演示,以补充已接受的答案:
我们可以制作一个小的可执行文件,它只显示调用它的参数,并将其放在 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
是单个 command-line 参数。
这对比于:
$ /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
和 re-invokes 同样的脚本)。