"#!/usr/bin/env bash"和“# ! / usr / bin / bash" ?

在Bash脚本的头文件中,这两个语句的区别是什么:

  1. # EYZ0

  2. # EYZ0

当我查阅env 手册页时,我得到了这样的定义:

 env - run a program in a modified environment

这是什么意思?

255774 次浏览

与在/usr/bin/bash/中显式定义解释器的路径不同,通过使用env命令,解释器将从第一次找到它的地方搜索并启动。这个有有利也有弊

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

通过/usr/bin/env运行命令的好处是查找当前environment中程序的默认版本。

这样,您就不必在系统上的特定位置查找它,因为这些路径可能位于不同系统上的不同位置。只要它在你的路上,它就会找到它。

一个缺点是,如果您希望支持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运行python脚本,而不必重写可执行行)。但是在安全性是重点的情况下,后者将是首选,因为它限制了代码注入的可能性。

我发现它很有用,因为当我不知道env的时候,在我开始写脚本之前,我是这样做的:

type nodejs > scriptname.js #or any other environment

然后我将文件中的这一行修改为shebang.
我这样做是因为我不总是记得nodejs在我的计算机上的位置——/usr/bin/或/bin/,所以对我来说env非常有用。也许这有细节,但这是我的原因

如果shell脚本以#!/bin/bash开始,它们将始终从/bin开始使用bash运行。然而,如果他们从#!/usr/bin/env bash开始,他们会在$PATH中搜索bash,然后从他们能找到的第一个开始。

为什么这有用呢?假设您想运行bash脚本,需要bash 4。x或更新的,但您的系统只有bash 3。X已安装,但当前您的发行版没有提供更新的版本,或者您不是管理员,无法更改系统上安装的内容。

当然,您可以下载bash源代码并从头构建自己的bash,例如将其放置在~/bin中。您还可以修改.bash_profile文件中的$PATH变量,以包括~/bin作为第一个条目(PATH=$HOME/bin:$PATH作为~将不会在$PATH中展开)。如果您现在调用bash, shell将首先按顺序在$PATH中查找它,因此它从~/bin开始,在那里它将找到bash。如果脚本使用$PATH2搜索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,你就有大麻烦了。

例如,假设一个工具创建了一个文件~/.evil/bash,内容如下:

#!/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语法。

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