#! 到底做了什么
当我们运行以 (又名 shebang )开头的文件时究竟会发生什么#!
,以及为什么有些人使用#!/usr/bin/env bash
。
#!
工作原理
shebang#!
用于告诉内核应该使用哪个解释器来运行文件中的命令。
当我们运行一个以 开头的文件时#!
,内核会打开该文件,并获取从 开始#!
到行末的内容。为了便于教学,我们假设它将command
从 shebang 开始到 行末结束的内容保存在一个名为 的字符串变量中。
在此之后,内核尝试运行一个命令,其中包含内容command
,并将我们要执行的文件的文件名作为第一个参数。
myscript.sh
因此,如果您有一个用一些 shell 命令调用的以 开头的可执行文件#!/bin/bash
,则当您运行它时,内核将执行/bin/bash myscript.sh
。
在下面的例子中,您将会非常清楚地看到这一点。
从经典开始hello.sh
:
#!/bin/bash
echo "Hello World!"
假设此文件具有可执行权限,当您在命令行中输入以下内容时:
$ ./hello.sh
内核会注意到#!
第一行中的 ,然后获取其后的内容,在本例中为/bin/bash
。然后执行的操作具有与此完全相同的效果:
$ /bin/bash hello.sh
让我们使用另一个示例#!/bin/cat
。文件名为shebangcat
:
#!/bin/cat
All the contents of this file will be
printed in the screen when it's executed
(including the '#!/bin/cat' in the first line).
让我们记住:
- 事情的后续是:
/bin/cat
- 文件名:
./shebangcat
因此执行的操作如下:/bin/cat ./shebangcat
亲自看看吧:
$ ./shebangcat
#!/bin/cat
All the contents of this file will be
printed in the screen when it's executed
(including the '#!/bin/cat' in the first line).
为了更清楚地说明我的意思,我们再举一个例子。以下文件名为shebangecho
:
#!/usr/bin/echo
The contents of this file will *NOT* be
printed when it's executed.
让我们检查一下:
$ ./shebangecho
./shebangecho
输出是文件的名称,因为这是内核执行的内容/usr/bin/echo ./shebangecho
。
另一件有趣的事情是,如果我们在调用脚本时传递参数,这些参数也会传递给内核执行的命令。正如我们在下面的例子中看到的shebangls.sh
:
#!/bin/ls
The contents here doesn't matter.
现在,当我们运行它时:
$ ./shebangls.sh
./shebangls.sh
$ ./shebangls.sh -l
-rwxr-xr-x 1 meleu meleu 41 Nov 28 14:42 ./shebangls.sh
$ ./shebangls.sh notfound
/bin/ls: cannot access 'notfound': No such file or directory
./shebangls.sh
为什么有些人使用#!/usr/bin/env
?
你可能见过一些脚本以 开头,#!/usr/bin/env bash
而你习惯于只看到#!/bin/bash
。这样做的原因是为了提高脚本的可移植性(尽管这是一个有争议的问题,正如我们将在下面看到的)。
如果该env
命令不带参数,则会打印出一个包含所有环境变量的(大)列表。但如果env
该命令后跟一个命令,则会在另一个 Shell 实例中运行该命令。
🤔 -好的,但是这会如何影响可移植性?!
当您使用 时,#!/bin/bash
您显然是在表示bash
位于/bin/
目录中。这似乎是所有 Linux 发行版的默认设置,但在其他 Unix 版本中,这种情况可能不会发生(例如bash
可以放在 中/usr/bin/
)。在这样的系统中,以 开头的脚本#!/bin/bash
会导致bad interpreter: No such file or directory
。
当你运行 时env bash
,会在变量中env
搜索,然后运行找到的第一个变量。通常在 中,但在其他系统上运行脚本的用户可以将其放在 中,甚至可以在 中测试其他版本。bash
$PATH
bash
/bin/
/usr/bin/
/home/user/bin/bash
因此,为了使该脚本具有更大的普及范围并在 Linux 以外的环境中使用,有些人建议使用该env
技术。
🤔 -可是等等!怎么保证 会env
一直在 呢/usr/bin/
?
没有任何保证...😇
此建议基于 Unix 系统中常见的情况。我看到/usr/bin/env
一些现代项目(例如RetroPie)中也使用了它,但它特别适用于运行 Python 甚至 NodeJS 脚本的情况。
让我们以这个 NodeJS 用法为例。我想通过调用脚本的文件名来调用一个 NodeJS 脚本。那么我可以这样做:
#!/usr/bin/node
console.log('Hello World from NodeJS');
问题是我通常通过Node 版本管理器安装 Node ,而不是使用发行版的包管理器。所以我的node
版本是这样的:
$ which node
/home/meleu/.nvm/versions/node/v14.15.1/bin/node
无论如何我都想把它写进#!/home/meleu/.nvm/versions/node/v14.15.1/bin/node
我的剧本里!
因此,这里的解决方案是使用#!/usr/bin/env node
。
#!
如果我根本不想使用怎么办?
我强烈建议您不要编写或运行没有 shebang 的 shell 脚本#!
!
正如我们所说,Shebang 会告诉内核使用哪个解释器来运行文件中的命令。如果你运行脚本时没有指定解释器,shell 会生成另一个实例并尝试运行脚本中的命令。这意味着它将执行文件中找到的所有命令,即使该文件是用 zsh、ksh、dash、fish、node、python 或其他语言编写的。
总结:脚本的开头一定要用#!
shebang。最好用#!/usr/bin/env
。
链接
- http://mywiki.wooledge.org/BashProgramming#Shebang
- https://wiki.bash-hackers.org/scripting/basics#the_shebang
- 这是丹尼斯·里奇 (Dennis Ritchie) 在 1980 年发来的一封电子邮件,其中讨论了这些“神奇人物”。
man env
- Node.js 工具