“#!/usr/bin/env bash”和“#!/usr/bin/bash”有什么区别?

622
在Bash脚本头文件中,这两个语句有什么区别?
  1. #!/usr/bin/env bash

  2. #!/usr/bin/bash

当我查看 envman手册时,我得到了以下定义:
 env - run a program in a modified environment

这是什么意思?


12
请看这个问题我的答案 - Keith Thompson
8
谁能告诉我为什么这个问题被关闭了,"与编程或软件开发有关"不是吗? - Salah Eddine Taouririt
39
这个问题不应该被标记为离题。只需要有5个得分超过3000的人将其标记为“符合主题”,它就可以重新打开。这是一个关于编程的问题。 - Danijel-James W
4
我震惊了。发现Linux文档充满了同义反复的说法。 https://xkcd.com/703/ http://git-man-page-generator.lokaltog.net/ - allyourcode
5
一个主要的区别是,根据系统,#!/usr/bin/bash可能无法正常工作。在我的系统(Ubuntu 17.04)上,bash/bin/bash,而没有 /usr/bin/bash/bin/usr/bin 之间的区别在很大程度上是随意的。另外,大多数系统将 env 命令放在 /usr/bin 中,但这并不保证。 - Keith Thompson
显示剩余4条评论
6个回答

495

通过/usr/bin/env运行命令的好处是它会在当前env环境中查找程序的默认版本,因此你不必在系统上特定的位置搜索它,由于这些路径在不同系统上可能位于不同的位置。只要它在你的路径中,就可以找到它。

其中一个缺点是如果你想支持Linux,你将无法传递多个参数(例如,你将无法编写/usr/bin/env awk -f),因为POSIX对如何解释这行的方式有歧义,而Linux将第一个空格后面的所有内容都解释为单个参数。你可以在某些版本的env上使用/usr/bin/env -S来解决这个问题,但脚本将变得更加不可移植,并且在相当新的系统上(例如,甚至是Ubuntu 16.04)也会出现问题。

另一个缺点是,由于你没有调用显式可执行文件,所以很容易出现错误,对于多用户系统来说还存在安全问题(例如,如果有人在你的路径中调用了名为bash的可执行文件)。

#!/usr/bin/env bash #lends you some flexibility on different systems
#!/usr/bin/bash     #gives you explicit control on a given system of what executable is called
在某些情况下,第一种方法可能更受欢迎(例如在运行具有多个Python版本的脚本时,而无需重新编写可执行行)。但在注重安全性的情况下,后者更受欢迎,因为它限制了代码注入的可能性。

33
另一个缺点是你无法向解释器传递附加参数。 - Keith Thompson
2
@KeithThompson:信息不正确。你可以使用/usr/bin/env向基础解释器传递选项! - Gaurav Agarwal
7
不在我的系统上。包含这一行代码的脚本:#!/usr/bin/env echo Hello会报错:/usr/bin/env: echo Hello: No such file or directory。显然,它将 echo Hello 视为 /usr/bin/env 的单个参数。 - Keith Thompson
5
@AndréLaszlo :env命令确实允许将参数传递给命令。问题在于#!行的语义,这取决于内核。最近的Linux内核确实允许像#!/usr/bin/env command args这样的东西,但旧的Linux内核和其他系统则不行。 - Keith Thompson
1
让命令更加突出?老实说,那是四年前的事情了,所以我没有正当的借口。在一个 shell 脚本中,你是正确的,它们不会存在,而且我承认我当时没有深入研究 SE 标记。 - Alec Bennett
显示剩余6条评论

116
使用#!/usr/bin/env NAME可以使shell在$PATH环境变量中查找第一个匹配的NAME。如果您不知道绝对路径或不想搜索绝对路径,它可能会很有用。

27
至少你得知道 env 是什么意思 :)。 - ᐅdevrimbaris
2
优秀的回答。简洁地解释了env shebang的作用,而不是仅说“根据您的系统配置选择程序”。 - De Novo
这是我遇到的情况。当我在script.sh的第一行使用“#!/usr/bin/env bash”运行命令“./script.sh”时,它会给出反馈“-bash:./tools/dist_train.sh:权限被拒绝”。但是当我运行命令“bash ./script.sh”时,它可以正常工作。你能告诉我为什么“#!/usr/bin/env NAME”不起作用吗? - Eric Kani
3
@EricKani:很可能你的"./script.sh"没有被标记为可执行文件(因此你不能单独执行它)。"bash ./script.sh"可以愉快地接受非可执行脚本文件。(解决方案:chmod +x ./script.sh - DevSolar

63
如果使用#!/bin/bash作为shell脚本的开头,它们将始终在/bin中运行bash。但如果使用#!/usr/bin/env bash开始,它们会在$PATH中搜索bash,然后使用找到的第一个来启动。
这有什么用处呢?假设你想要运行需要bash 4.x或更新版本的脚本,但你的系统只安装了bash 3.x,而你的发行版又没有提供更新版本,或者你不是管理员,无法更改所安装的系统软件包。当然,你可以下载bash源代码并从头构建自己的bash,例如将其放置在~/bin中。你还可以修改.bash_profile文件中的$PATH变量,将~/bin作为第一个条目(PATH=$HOME/bin:$PATH,因为~$PATH中不会扩展)。现在,如果你调用bash,shell会首先按顺序在$PATH中查找,所以它会从~/bin开始,在那里能够找到你的bash。如果脚本使用#!/usr/bin/env bash搜索bash,则同样的事情会发生,因此这些脚本现在将使用你自定义的bash版本在你的系统上工作。
其中一个缺点是,这可能会导致意外的行为。例如,在不同的环境或具有不同搜索路径的用户上运行相同的脚本可能会使用不同的解释器,从而引起各种头痛。 env的最大缺点是某些系统仅允许一个参数,因此你不能这样做:#!/usr/bin/env <interpreter> <arg>,因为系统将把<interpreter> <arg>视为一个参数(它将像表达式被引用一样处理),因此env会搜索名为<interpreter> <arg>的解释器。请注意,这不是env命令本身的问题,因为它始终允许通过多个参数传递,但是系统的shebang解析器会在调用env之前解析该行。目前,这已在大多数系统上得到解决,但如果你的脚本想要超级可移植,你不能依赖于该问题已经在你将要运行的系统上得到解决。
它甚至可能产生安全隐患,例如如果sudo没有配置为清理环境或者将$PATH排除在清理之外。举个例子:
通常,/bin是一个受保护的位置,只有root能够更改其中的内容。但你的主目录不是,任何你运行的程序都可以对其进行更改。这意味着恶意代码可以将一个虚假的bash放置到某个隐藏的目录中,并修改你的.bash_profile,将该目录包含在你的$PATH中,因此所有使用#!/usr/bin/env bash的脚本都会以该虚假的bash运行。如果sudo保留了$PATH,那
#!/bin/bash

if [ $EUID -eq 0 ]; then
  echo "All your base are belong to us..."
  # We are root - do whatever you want to do
fi

/bin/bash "$@"

让我们编写一个简单的脚本sample.sh

#!/usr/bin/env bash

echo "Hello World"

证明概念(在一个 sudo 保持 $PATH 的系统上):

$ ./sample.sh
Hello World

$ sudo ./sample.sh
Hello World

$ export PATH="$HOME/.evil:$PATH"

$ ./sample.sh
Hello World

$ sudo ./sample.sh
All your base are belong to us...
Hello World
通常经典的shell应该全部位于/bin,如果由于某种原因不想将它们放在那里,可以在/bin中放置指向其实际位置的符号链接(或者/bin本身就是一个符号链接),所以我总是使用#!/bin/sh#!/bin/bash。如果这些东西不能工作,太多问题会出现。虽然POSIX不要求这些位置(POSIX不标准化路径名,因此根本不标准化shebang功能),但它们是如此常见,即使系统没有提供/bin/sh,它也可能仍然理解#!/bin/sh并且知道该做什么,甚至可能仅为了与现有代码兼容性。
但对于更现代、非标准、可选的解释器,如Perl、PHP、Python或Ruby,则没有任何地方指定它们应该位于哪里。它们可能在/usr/bin中,也可能在/usr/local/bin中,或者在完全不同的层次结构分支中(/opt/.../Applications/...等)。这就是为什么它们经常使用#!/usr/bin/env xxx shebang语法的原因。

感谢@Mecki,迄今为止最佳答案,特别是演示了包含“所有你的基地都属于我们”的部分有多容易。真正的黑帽骇客不会添加那部分,只会尝试利用任何漏洞(无论是技术上还是个人上 - 参见社交工程)。我使用的是shebangs #!/bin/sh和#!/bin/bash - 如果有人抱怨,我会告诉他们要么自己编辑脚本,要么在/bin中创建符号链接 - 或者要求他们的系统管理员在/bin中创建符号链接。 - Rava
好的回答。这基本上就是为什么在第一位放置 . 是一个非常糟糕的做法。即使你将其放在 PATH 的最后一位,它以后可能不再是最后一位了。 - Anders

16

与其明确定义解释器的路径,例如/usr/bin/bash/,使用env命令会在第一次发现它的位置搜索并启动解释器。 这种方法既有优点也有缺点。(了解更多)


在大多数系统上,它们的功能是相同的,但这取决于您的 bash 和 env 可执行文件的位置。不确定这会如何影响环境变量。 - safay
2
可以通过提供解释器的完整路径来指定解释器,而不使用env。问题在于,在不同的计算机系统上,确切的路径可能是不同的。相反,使用env,解释器在运行脚本时被搜索和定位。这使得脚本更具可移植性,但也增加了选择错误解释器的风险,因为它在可执行搜索路径上的每个目录中搜索匹配项。它也面临着相同的问题,即env二进制文件的路径在每台机器上也可能不同。 - Mike Clark

4
我认为这很有用,因为在我了解环境变量之前,开始编写脚本时我会这样做:
type nodejs > scriptname.js #or any other environment

然后我在文件中修改了那一行变成了shebang。
我这样做是因为我不总记得nodejs在我的电脑上的位置是 /usr/bin/ 还是 /bin/,所以对我来说 env 非常有用。可能有细节需要注意,但这就是我的原因。


1
一个原因是它可以在Linux和BSD之间进行移植。

可以举个例子说明为什么它使得程序具有可移植性。这不仅是为了在Linux和BSD之间保持兼容性,也是为了在不同的Linux发行版之间保持兼容性。但是,你说得对。 - Anders
当我需要移植到BSD时,我找到了相关的参考资料。我在几个Linux发行版上进行了实证验证。对于BSD,我尝试了GhostBSD和Mac(这是一种类似的Linux移植)。显然这并不是一个令人满意的答案。 - Scott Franco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接