7_R*_*R3X 23 shell bash zsh shell-script shebang
假设我帐户的默认 shell 是 zsh,但我打开了终端并启动了 bash 并执行了一个名为 的脚本prac002.sh,哪个 shell 解释器将用于执行脚本,zsh 还是 bash?考虑以下示例:
papagolf@Sierra ~/My Files/My Programs/Learning/Shell % sudo cat /etc/passwd | grep papagolf
[sudo] password for papagolf:
papagolf:x:1000:1001:Rex,,,:/home/papagolf:/usr/bin/zsh
# papagolf's default shell is zsh
papagolf@Sierra ~/My Files/My Programs/Learning/Shell % bash
# I fired up bash. (See that '%' prompt in zsh changes to '$' prompt, indicating bash.)
papagolf@Sierra:~/My Files/My Programs/Learning/Shell$ ./prac002.sh
Enter username : Rex
Rex
# Which interpreter did it just use?
Run Code Online (Sandbox Code Playgroud)
**编辑:** 这是脚本的内容
papagolf@Sierra ~/My Files/My Programs/Learning/Shell % cat ./prac002.sh
read -p "Enter username : " uname
echo $uname
Run Code Online (Sandbox Code Playgroud)
Mic*_*mer 24
因为脚本没有以#!指示使用哪个解释器的shebang 行开头,所以POSIX 说:
如果该
execl()函数由于与 POSIX.1-2008 的系统接口卷中定义的 [ENOEXEC] 错误等效的错误而失败,则 shell 应执行一个命令,该命令等效于调用 shell,并将搜索结果的路径名作为其第一个调用操作数,将任何剩余的参数传递给新外壳,除了新外壳中的“$0”值可以设置为命令名称。如果可执行文件不是文本文件,shell 可能会绕过此命令执行。在这种情况下,它将写入错误消息,并返回退出状态 126。
那个措辞有点模棱两可,不同的壳有不同的解释。
在这种情况下,Bash 将使用它自己运行脚本。另一方面,如果您改为从 zsh 运行它,则zsh 将使用sh(无论您的系统上是什么)。
您可以通过将这些行添加到脚本来验证这种情况下的行为:
echo $BASH_VERSION
echo $ZSH_VERSION
Run Code Online (Sandbox Code Playgroud)
您会注意到,在 Bash 中,第一行输出您的版本,而第二行从不说什么,无论您使用哪种 shell。
/bin/sh是,例如,dash那么当从 zsh 或 dash 执行脚本时,这两行都不会输出任何内容。/bin/sh是 Bash 的链接,您将在所有情况下看到第一行输出。/bin/sh是一个不同版本的Bash比你直接使用,你会看到不同的输出,当你从bash的直接和zsh中运行该脚本。ps -p $$来自 rools 答案的命令还将显示有关 shell 用于执行脚本的命令的有用信息。
由于该文件不属于系统识别的任何类型的可执行文件,并且假设您已获得执行该文件的权限,execve()系统调用通常会因ENOEXEC(非可执行文件)错误而失败。
然后发生什么取决于用于执行命令的应用程序和/或库函数。
例如,它可以是一个 shell,即execlp()/ execvp()libc 函数。
大多数其他应用程序在运行命令时将使用其中任何一个。例如,他们将通过system("command line")libc 函数调用 shell,该函数通常会调用sh来解析该命令行(其路径可以在编译时确定(如/bin/shvs /usr/xpg4/bin/shSolaris 上)),或者调用$SHELL自己存储的 shell,如vi使用它的 !命令或xterm -e 'command line'许多其他命令(su user -c将调用用户的登录 shell 而不是$SHELL)。
通常,不以开头的无shebang 文本文件#被视为sh脚本。这sh是会有所不同,虽然。
execlp()/ execvp(),execve()返回ENOEXEC时通常会调用sh它。对于具有多个sh标准的系统,因为它们可以符合多个标准,这sh通常在编译时确定(应用程序使用execvp()/execlp()通过链接不同的代码块,该代码块指向 的不同路径sh)。例如,在 Solaris 上,这将是/usr/xpg4/bin/sh(标准,POSIX sh)或/bin/sh(Solaris 10 及更早版本上的 Bourne shell(过时的 shell),Solaris 11 中的 ksh93)。
说到贝壳,有很多变化。bash, AT&T ksh, Bourne shell 通常会exec在模拟 a 后自行解释脚本(在子进程中,除非使用)execve(),即取消设置所有未导出的变量,关闭所有 close-on-exec fds,删除所有自定义陷阱,别名、函数...(bash将以sh模式解释脚本)。yash将执行自身(在模式中sh也是argv[0]如此sh)以解释它。
zsh, pdksh, ash-based shells 通常会调用sh(其路径在编译时确定)。
对于csh和tcsh(以及sh一些早期 BSD 的),如果文件的第一个字符是#,那么它们将执行自己来解释它,sh否则。这可以追溯到 Shebang 之前的时间,在那里csh确实识别#为注释而不是 Bourne shell,因此#暗示它是一个 csh 脚本。
fish(至少版本 2.4.0),如果execve()失败则返回错误(它不会尝试将其视为脚本)。
某些 shell(如bash或 AT&T ksh)将首先尝试启发式地确定文件是否可能是脚本。因此,您可能会发现,如果脚本的前几个字节包含 NUL 字符,则某些 shell 将拒绝执行该脚本。
另请注意,如果execve()ENOEXEC 失败但文件确实有 shebang 行,则某些 shell 会尝试自己解释该 shebang 行。
所以举几个例子:
$SHELLis 时/bin/bash,xterm -e 'myscript with args'将被myscript解释为bashinsh模式。与xterm -e myscript with args,xterm将使用,execvp()因此脚本将由sh.su -c myscript在Solaris 10,其中root的登录外壳是/bin/sh与/bin/sh是伯恩壳将有myscript由Bourne shell的解释。/usr/xpg4/bin/awk 'BEGIN{system("myscript")'在 Solaris 10 上,它将由/usr/xpg4/bin/sh(与 相同/usr/xpg4/bin/env myscript)解释。find . -prune -exec myscript {} \;在 Solaris 10 上(使用execvp()),即使在 POSIX 环境中(一致性错误),它也会被/bin/sheven with解释/usr/xpg4/bin/find。csh -c myscriptcsh如果以 开头,则将对其进行解释#,sh否则将对其进行解释。总而言之,如果您不知道将如何调用以及通过何种方式调用该脚本,则无法确定将使用哪个 shell 来解释该脚本。
在任何情况下,read -p都是bash-only 语法,因此您需要确保脚本被解释bash(并避免误导性.sh扩展)。您知道bash可执行文件的路径并使用:
#! /path/to/bash -
read -p ...
Run Code Online (Sandbox Code Playgroud)
或者您可以尝试通过使用以下命令来$PATH查找bash可执行文件(假设bash已安装):
#! /usr/bin/env bash
read -p ...
Run Code Online (Sandbox Code Playgroud)
(env在 中几乎无处不在/usr/bin)。或者,您可以使其与 POSIX+Bourne 兼容,在这种情况下,您可以使用/bin/sh. 所有系统都会有一个/bin/sh. 在大多数情况下,它将(在大多数情况下)与 POSIX 兼容,但您可能仍然时不时地在那里找到 Bourne shell。
#! /bin/sh -
printf >&2 'Enter a user name: '
read user
printf '%s\n' "$user"
Run Code Online (Sandbox Code Playgroud)