Sto*_*row 13 shell ps shell-script shebang
当我运行这个脚本时,打算一直运行直到被杀死......
# foo.sh
while true; do sleep 1; done
Run Code Online (Sandbox Code Playgroud)
...我无法找到它使用ps ax
:
>./foo.sh
// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3 S+ 0:00 grep --color=auto foo.sh
Run Code Online (Sandbox Code Playgroud)
...但如果我只是将常见的“ #!
”头添加到脚本中...
#! /usr/bin/bash
# foo.sh
while true; do sleep 1; done
Run Code Online (Sandbox Code Playgroud)
...然后脚本可以通过相同的ps
命令找到...
>./foo.sh
// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43 S+ 0:00 /usr/bin/bash ./foo.sh
21324 pts/3 S+ 0:00 grep --color=auto foo.sh
Run Code Online (Sandbox Code Playgroud)
为什么会这样?
这可能是一个相关的问题:我认为“ #
”只是一个评论前缀,如果是这样,“ #! /usr/bin/bash
”本身只不过是一个评论。但是“ #!
”是否具有比评论更重要的意义?
Kus*_*nda 14
当当前交互式 shell 是bash
,并且您运行一个没有#!
-line的脚本时,bash
将运行该脚本。该过程将在ps ax
输出中显示为bash
.
$ cat foo.sh
# foo.sh
echo "$BASHPID"
while true; do sleep 1; done
$ ./foo.sh
55411
Run Code Online (Sandbox Code Playgroud)
在另一个终端:
$ ps -p 55411
PID TT STAT TIME COMMAND
55411 p2 SN+ 0:00.07 bash
Run Code Online (Sandbox Code Playgroud)
有关的:
相关章节构成bash
手册:
如果此执行失败,因为文件不是可执行格式,并且文件不是目录,则假定它是 shell script,一个包含 shell 命令的文件。 产生一个子shell来执行它。这个子shell重新初始化自己,所以效果就像一个新的shell被调用来处理脚本,除了父级记住的命令的位置(参见下面SHELL BUILTIN COMMANDS下的哈希)由子级保留。
如果程序是以 开头的文件
#!
,则第一行的其余部分指定程序的解释器。 shell在自己不处理此可执行格式的操作系统上执行指定的解释器。[...]
这意味着./foo.sh
在命令行上运行,当foo.sh
没有#!
-line 时,与在子 shell 中运行文件中的命令相同,即作为
$ ( echo "$BASHPID"; while true; do sleep 1; done )
Run Code Online (Sandbox Code Playgroud)
用适当的线#!
指向 eg /bin/bash
,它就像做
$ /bin/bash foo.sh
Run Code Online (Sandbox Code Playgroud)
Gil*_*il' 12
当 shell 脚本以 开头时#!
,就 shell 而言,第一行是注释。然而,前两个字符对系统的另一部分有意义:内核。这两个字符#!
被称为shebang。要了解shebang的作用,您需要了解程序是如何执行的。
从文件执行程序需要内核的操作。这是作为execve
系统调用的一部分完成的。内核需要验证文件权限,释放与当前正在调用进程中运行的可执行文件相关的资源(内存等),为新的可执行文件分配资源,并将控制权转移给新程序(以及更多需要执行的操作)我就不提了)。该execve
系统调用替换当前运行的进程的代码; 有一个单独的系统调用fork
来创建一个新进程。
为此,内核必须支持可执行文件的格式。该文件必须包含机器代码,以内核理解的方式组织。shell 脚本不包含机器代码,因此不能以这种方式执行。
shebang 机制允许内核将解释代码的任务推迟到另一个程序。当内核看到可执行文件以 开头时#!
,它会读取接下来的几个字符并将文件的第一行(减去前导#!
和可选空格)解释为另一个文件的路径(加上参数,我不会在这里讨论)。当内核被告知要执行该文件/my/script
,并且它看到该文件以该行开头时#!/some/interpreter
,内核就会执行/some/interpreter
该参数/my/script
。然后由它/some/interpreter
来决定/my/script
它应该执行的脚本文件。
如果文件既不包含内核可以理解的格式的本机代码,也不以 shebang 开头怎么办?那么,该文件不可执行,execve
系统调用失败并显示错误代码ENOEXEC
(可执行格式错误)。
这可能是故事的结尾,但大多数 shell 实现了后备功能。如果内核返回ENOEXEC
,shell 会查看文件的内容并检查它是否看起来像 shell 脚本。如果 shell 认为文件看起来像 shell 脚本,它会自行执行它。它如何执行此操作的详细信息取决于外壳。您可以通过添加ps $$
脚本来查看发生的一些事情,并通过使用strace -p1234 -f -eprocess
1234 是 shell 的 PID来观察进程来查看更多信息。
在 bash 中,这种回退机制是通过调用fork
而不是execve
. 子 bash 进程自行清除其内部状态并打开新的脚本文件以运行它。因此,运行脚本的进程仍然使用原始 bash 代码映像和最初调用 bash 时传递的原始命令行参数。ATT ksh 的行为方式相同。
% bash --norc
bash-4.3$ ./foo.sh
PID TTY STAT TIME COMMAND
21913 pts/2 S+ 0:00 bash --norc
Run Code Online (Sandbox Code Playgroud)
相反,DashENOEXEC
通过调用/bin/sh
作为参数传递的脚本路径来做出反应。换句话说,当您从 dash 执行 shebangless 脚本时,它的行为就像脚本有一个带有#!/bin/sh
. Mksh 和 zsh 的行为方式相同。
% dash
$ ./foo.sh
PID TTY STAT TIME COMMAND
21427 pts/2 S+ 0:00 /bin/sh ./foo.sh
Run Code Online (Sandbox Code Playgroud)