首先,让我们同步一些信息和词汇表:
- 操作系统:Ubuntu 16
- 外壳:破折号
- 正常程序:在
sh
和Search and Execution
的手册中,命令分为三种类型,其中一种是常规程序
我的困惑来自于对正常程序如何执行的描述,摘录是
否则,如果命令名称与函数或内置程序不匹配,则会将该命令作为文件系统中的普通程序进行搜索(如下一节所述((1(当执行一个普通程序时,shell运行该程序,将参数和环境传递给该程序(2(如果程序不是一个正常的可执行文件(即,如果它不是以ASCII表示为"#!"的"幻数"开头,那么execve(2(返回ENOEXEC(,shell将在子shell中解释程序在这种情况下,子shell将重新初始化自己,这样的效果就像调用了一个新的shell来处理ad-hoc shell脚本一样,只是位于父shell中的散列命令的位置将被子shell记住。
我强调两个有编号的句子以使它们清楚。
根据我之前学到的,shell总是会生成一个子shell来执行正常程序。然而,(1(语句似乎表示当前shell使用execve
直接运行程序,如(2(所示。只有当程序不包含shebang行时,它才会在子shell中执行。
为了练习和实验,我编写了两个简单的shell脚本test.sh和subtest.sh
#!/bin/sh
ps -o pid,ppid,args -p $$
./subtest.sh
#!/bin/sh
ps -o pid,ppid,args -p $$
pstree
我得到的是:
- 子测试是测试的子过程
- pstree显示测试.sh-子测试.sh-pstree
然后,从子测试中删除shebang。sh并再次运行测试。sh:
- 子测试仍然是测试的子过程
- pstree显示test.sh--sh--pstree
所以,不同的是subtest.sh->仅限sh。接下来,我将转到strace
克隆和执行。结果粘贴在下方
execve("./test.sh", ["./test.sh"], [/* 88 vars */]) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f2456b559d0) = 25816
strace: Process 25816 attached
[pid 25816] execve("/bin/ps", ["ps", "-o", "pid,ppid,args", "-p", "25815"], [/* 88 vars */]) = 0
PID PPID COMMAND
25815 25813 /bin/sh ./test.sh
[pid 25816] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=25816, si_uid=1000, si_status=0, si_utime=0, si_stime=3} ---
clone(strace: Process 25831 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f2456b559d0) = 25831
[pid 25831] execve("./subtest.sh", ["./subtest.sh"], [/* 88 vars */]) = 0
[pid 25831] clone(strace: Process 25832 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fc6e22bb9d0) = 25832
[pid 25832] execve("/bin/ps", ["ps", "-o", "pid,ppid,args", "-p", "25831"], [/* 88 vars */]) = 0
PID PPID COMMAND
25831 25815 /bin/sh ./subtest.sh
[pid 25832] +++ exited with 0 +++
[pid 25831] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=25832, si_uid=1000, si_status=0, si_utime=0, si_stime=2} ---
[pid 25831] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=25831, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
execve("./test.sh", ["./test.sh"], [/* 88 vars */]) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f960b2769d0) = 24702
strace: Process 24702 attached
[pid 24702] execve("/bin/ps", ["ps", "-o", "pid,ppid,args", "-p", "24701"], [/* 88 vars */]) = 0
PID PPID COMMAND
24701 24699 /bin/sh ./test.sh
[pid 24702] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=24702, si_uid=1000, si_status=0, si_utime=1, si_stime=3} ---
clone(strace: Process 24703 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f960b2769d0) = 24703
[pid 24703] execve("./subtest.sh", ["./subtest.sh"], [/* 88 vars */]) = -1 ENOEXEC (Exec format error)
[pid 24703] execve("/bin/sh", ["/bin/sh", "./subtest.sh"], [/* 88 vars */]) = 0
[pid 24703] clone(strace: Process 24704 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f493e5bc9d0) = 24704
[pid 24704] execve("/bin/ps", ["ps", "-o", "pid,ppid,args", "-p", "24703"], [/* 88 vars */]) = 0
PID PPID COMMAND
24703 24701 /bin/sh ./subtest.sh
[pid 24704] +++ exited with 0 +++
[pid 24703] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=24704, si_uid=1000, si_status=0, si_utime=0, si_stime=1} ---
[pid 24703] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=24703, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
除了w/o shebang调用两个execve
之外,大部分输出都是相同的(一个用于subtest.sh,然后用于sh(。我认为理解正常程序是如何执行的关键就在这里。
- 不过,shell总是生成一个子shell,由
clone
调用推断 - 如果有shebang说明符,它会影响要使用的shell解释器,否则,该解释器与父解释器是相同的副本
正常。说了很多话之后,我认为手册中的描述有点误导,但我不确定。正常程序是如何执行的?我的解释正确吗?
谢谢!
更新
是的,我把自己限制在只带或不带shebang的脚本中。如果调用二进制文件,例如
#!/bin/sh
date -u
则来自strace
的结果是clone
和execve
。
execve("./test.sh", ["./test.sh"], [/* 88 vars */]) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe4ab21a9d0) = 1218
strace: Process 1218 attached
[pid 1218] execve("/bin/date", ["date", "-u"], [/* 88 vars */]) = 0
Tue Jun 29 07:31:24 UTC 2021
[pid 1218] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1218, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
也许,我可以总结一下:
- 它总是生成子流程
- 子流程是否为子shell取决于
- 如果程序是二进制程序,或者是带有shebang说明符的脚本,那么它就是一个子进程
- 如果程序是没有shebang说明符的脚本,那么它就是一个子shell
我认为引用的文章试图说的是
- shell试图将文件作为可执行文件传递给内核
- 如果失败,shell将重新尝试将文件作为脚本运行
本机二进制文件的常见情况在阐述中完全缺失,而且可能至关重要。如果文件具有内核知道如何执行的神奇签名,则第一种情况将成功;特别是#!
魔术签名的存在只是一个特定的广义情况,我认为在第1项中包括了查找本地二进制文件(如果您的体系结构是ELF,则是ELF可执行文件等(,并且几乎是标准情况。
手册页是有用的剩余部分,阅读起来很好——并不总是(不…很少(准确和具体。您可能想要阅读POSIX-sh命令搜索和执行。
也许在更多的编程步骤中:
- 它总是产生一个子进程-创建一个"独立执行环境"-CCD_ 11
- 然后它调用
exec(name_of_file)
- (例如,如果内核不支持shebang,shell可以自己解析
#!
shebang行,并从中提取可执行文件名和exec
( - 如果失败,请致电
exec(sh, name of file)