为什么是#!/usr/bin/env bash优于#!/bin/bash?

我在很多地方看到过,包括本网站(什么是首选的Bash shebang?)上的建议,优先使用#!/usr/bin/env bash而不是#!/bin/bash。我甚至看到一个进取的个人建议使用#!/bin/bash was wrong和bash功能将失去这样做。

尽管如此,我在一个严格控制的测试环境中使用bash,其中流通的每个驱动器本质上都是单个主驱动器的克隆。我理解便携性的观点,尽管它并不一定适用于我的情况。是否有其他原因更喜欢__abc0而不是替代方案,假设可移植性是一个问题,是否有任何原因使用它可能破坏功能?

106413 次浏览

对于调用bash,这有点过分了。除非你在~/bin中有多个像你自己的bash二进制文件,但这也意味着你的代码依赖于$PATH中有正确的东西。

不过,它对于python之类的东西很方便。有一些包装器脚本和环境会导致使用替代的python二进制文件。

但是使用二进制文件的确切路径不会丢失任何东西,只要您确定它是您真正想要的二进制文件。

#!/usr/bin/envPATH中搜索bash,而bash并不总是在/bin中,特别是在非linux系统中。例如,在我的OpenBSD系统上,它在/usr/local/bin中,因为它是作为可选包安装的。

如果你绝对确定bash/bin中,并且永远都在,直接把它放在你的shebang中也没有坏处——但我不建议这样做,因为脚本和程序都有超出我们最初想象的生命。

bash的标准位置是/bin,我怀疑在所有系统上都是这样。但是,如果您不喜欢这个版本的bash呢?例如,我想使用bash 4.2,但我的Mac上的bash是3.2.5。

我可以尝试在/bin中重新安装bash,但这可能是一个坏主意。如果我更新我的操作系统,它将被覆盖。

然而,我可以在/usr/local/bin/bash中安装bash,并将我的PATH设置为:

PATH="/usr/local/bin:/bin:/usr/bin:$HOME/bin"

现在,如果我指定bash,我就不会在/bin/bash处得到那个破旧的对象,而是在/usr/local/bin处得到那个更新的、漂亮的对象。好了!

除了我的shell脚本有!# /bin/bash shebang。因此,当我运行shell脚本时,我得到的是旧而糟糕的bash版本,它甚至没有关联数组。

使用/usr/bin/env bash将使用在我的PATH中找到的bash版本。如果我设置我的PATH,那么/usr/local/bin/bash将被执行,这就是我的脚本将使用的bash。

在bash中很少看到这种情况,但在Perl和Python中更常见:

  • 某些Unix/Linux版本侧重于稳定,有时会远远落后于这两种脚本语言的发布。不久前,RHEL的Perl是5.8.8——这是Perl的一个8年前的版本!如果有人想使用更现代的功能,你必须安装自己的版本。
  • 像Perlbrew和Pythonbrew这样的程序允许您安装这些语言的多个版本。它们依赖于操作PATH以获得所需版本的脚本。硬编码路径意味着我不能在酿造下运行脚本。
  • 不久以前(好吧,是很久以前),Perl和Python还不是大多数Unix系统中包含的标准包。这意味着您不知道这两个程序安装在哪里。是在/bin下吗?/usr/bin吗?/opt/bin吗?谁知道呢?使用#! /usr/bin/env perl意味着我不需要知道。

现在为什么你不应该使用#! /usr/bin/env bash

当路径在shebang中被硬编码时,我必须使用该解释器运行。因此,#! /bin/bash迫使我使用默认安装的bash版本。由于bash特性非常稳定(尝试运行2。Python 3.x下的Python脚本的x版本),我的特定BASH脚本不太可能不起作用,而且由于我的BASH脚本可能被这个系统和其他系统使用,使用非标准版本的BASH可能会产生意想不到的效果。我很可能希望确保bash的稳定标准版本与我的shell脚本一起使用。因此,我可能想在我的shebang中硬编码路径。

有很多系统在/bin中没有Bash, FreeBSD和OpenBSD只是举几个例子。如果你的脚本打算移植到许多不同的unix,你可能想要使用#!/usr/bin/env bash而不是#!/bin/bash

注意,这并不适用于sh;对于与bourne兼容的脚本,我只使用#!/bin/sh,因为我认为几乎所有存在的Unix在/bin中都有sh

 #!/usr/bin/env bash

肯定更好,因为它从系统环境变量中找到bash可执行路径。

转到Linux shell并键入

env

它将打印所有环境变量。

转到shell脚本并键入

echo $BASH

它将打印您的bash路径(根据环境变量列表),您应该使用该路径在脚本中构建正确的shebang路径。

我更喜欢将主程序包装在如下脚本中,以检查系统上所有可用的bash。最好对它使用的版本有更多的控制。

#! /usr/bin/env bash


# This script just chooses the appropriate bash
# installed in system and executes testcode.main


readonly DESIRED_VERSION="5"


declare all_bash_installed_on_this_system
declare bash


if [ "${BASH_VERSINFO}" -ne "${DESIRED_VERSION}" ]
then
found=0


all_bash_installed_on_this_system="$(\
awk -F'/' '$NF == "bash"{print}' "/etc/shells"\
)"


for bash in $all_bash_installed_on_this_system
do
versinfo="$( $bash -c 'echo ${BASH_VERSINFO}' )"
[ "${versinfo}" -eq "${DESIRED_VERSION}" ] && { found=1 ; break;}
done
if [ "${found}" -ne 1 ]
then
echo "${DESIRED_VERSION} not available"
exit 1
fi
fi


$bash main_program "$@"

通常# !路径/ /命令将触发bash在执行时将命令路径前置到调用脚本。的例子,

# file.sh
#!/usr/bin/bash
echo hi

./file.sh将启动一个新进程,脚本将像/bin/bash ./file.sh一样执行

现在

# file.sh
#!/usr/bin/env bash
echo hi

将被执行为/usr/bin/env bash ./file.sh,引用env的手册页将其描述为:

环境-在修改过的环境中运行程序

因此,env将在其环境变量PATH中查找命令bash,并在单独的环境中执行,其中环境值可以像NAME=VALUE对一样传递给env

你可以使用python等不同的解释器来测试其他脚本。

#!/usr/bin/env python
# python commands

你的问题是有偏见的,因为它假设#!/usr/bin/env bash优于#!/bin/bash。这种假设是不正确的,原因如下:

env在两种情况下有用:

    当解释器有多个不兼容的版本时,

  1. 例如python 2 / 3 perl 4 / 5php 5 / 7李< / >
  2. 当位置依赖于PATH时,例如在python虚拟环境中。

但bash不属于这两种情况,因为:

  1. bash非常稳定,特别是在Linux和BSD等现代系统上,它们构成了bash安装的绝大多数。
  2. 通常只有一个版本的bash安装在/bin下。
    这是过去20多年的情况,只有非常老的unices(没有人再使用了)有不同的位置

因此,通过/usr/bin/env遍历PATH变量对bash没有用处。

再加上这三个使用#!/bin/bash的好理由:

    对于PATH变量可能不包含/bin的系统脚本(当不使用sh时),

  1. 例如,cron默认为非常严格的/usr/bin:/binPATH,这当然没问题,但其他上下文/环境可能由于某些特殊原因不包括/bin
  2. 当用户搞砸了他的PATH时,这在初学者中很常见。
  3. 为了安全性,例如,当您调用调用bash脚本的suid程序时。你不希望通过PATH变量找到解释器,这完全在用户的控制之下!
最后,有人可能会争辩说,env生成bash有一个合法的用例:当需要使用#!/usr/bin/env -S VAR=value bash将额外的环境变量传递给解释器时。
但这不是bash的事情,因为当你在控制shebang时,你也在控制整个脚本,所以只需在脚本中添加VAR=value,以避免前面提到的bash脚本由env引入的问题