我在很多地方看到过,包括在这个网站上的推荐(什么是首选的Bash shebang?),优先使用#!/usr/bin/env bash
而不是#!/bin/bash
。我什至看到一个有进取心的人建议使用#!/bin/bash
是错误的,这样做会丢失 bash 功能。
综上所述,我在严格控制的测试环境中使用 bash,其中流通中的每个驱动器本质上都是单个主驱动器的克隆。我理解可移植性论点,尽管它不一定适用于我的情况。是否有任何其他理由更喜欢#!/usr/bin/env bash
而不是替代方案,并且假设可移植性是一个问题,是否有任何理由使用它可能会破坏功能?
#!/usr/bin/env
PATH
搜索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 版本!如果有人想使用更现代的功能,您必须安装自己的版本。
- 像Perlbrew和Pythonbrew这样的程序允许你安装这些语言的多个版本。它们依赖于操作您的 PATH 来获取所需版本的脚本。对路径进行硬编码意味着我无法在酿造下运行我的脚本。
- 不久前(好吧,很久以前),Perl和Python还不是大多数Unix系统中包含的标准包。这意味着您不知道这两个程序的安装位置。是在
/bin
下吗?/usr/bin
?/opt/bin
?谁知道呢?使用#! /usr/bin/env perl
意味着我不必知道。
现在为什么你不应该使用#! /usr/bin/env bash
当路径在 shebang 中被硬编码时,我必须使用该解释器运行。因此,#! /bin/bash
迫使我使用默认安装的 bash 版本。由于 bash 功能非常稳定(尝试在 Python 3.x 下运行 Python 脚本的 2.x 版本),因此我的特定 BASH 脚本不太可能不起作用,并且由于我的 bash 脚本可能被这个系统和其他系统使用,使用非标准版本的 bash 可能会产生不良影响。我很可能想确保 bash 的稳定标准版本与我的 shell 脚本一起使用。因此,我可能想在我的 shebang 中对路径进行硬编码。
有很多系统在/bin
中没有 Bash,FreeBSD 和 OpenBSD 仅举几例。如果您的脚本旨在移植到许多不同的 Unice,您可能希望使用#!/usr/bin/env bash
而不是#!/bin/bash
。
请注意,这不适用于sh
;对于Bourne兼容的脚本,我只使用#!/bin/sh
,因为我认为几乎每个存在的Unix都有sh
/bin
。
对于调用bash
来说,这有点矫枉过正。除非你在 ~/bin 中有多个像你自己的二进制文件这样的bash
二进制文件,但这也意味着你的代码取决于$PATH有正确的东西。
不过,对于像python
这样的事情很方便。有一些包装脚本和环境会导致使用替代python
二进制文件。
但是,只要您确定它是您真正想要的二进制文件,使用二进制文件的确切路径就不会丢失任何内容。
#!/usr/bin/env bash
肯定更好,因为它从系统环境变量中找到 bash 可执行路径。
转到您的 Linux shell,然后键入
env
它将打印所有环境变量。
转到您的 shell 脚本并键入
echo $BASH
它将打印您的 bash 路径(根据环境变量列表),您应该使用该路径在脚本中构建正确的 shebang 路径。
你的问题是有偏见的,因为它假设#!/usr/bin/env bash
优于#!/bin/bash
。这个假设是不正确的,原因如下:
env
在两种情况下很有用:
- 当有多个版本的解释器不兼容时.
例如python
2
/3
,perl
4
/5
, 或php
5
/7
- 当位置取决于
PATH
时,例如使用python虚拟环境。
但是 bash 不属于这两种情况中的任何一种,因为:
bash
非常稳定,尤其是在Linux和BSD等现代系统上,它们构成了绝大多数bash
安装。- 通常只有一个版本的
bash
安装在/bin
.
在过去的 20+ 年里一直如此,只有非常古老的 unices(没有人再使用)有不同的位置。
因此,通过/usr/bin/env
遍历PATH
变量对 bash 没有用。
加上这三个好的共鸣来使用#!/bin/bash
:
- 对于系统脚本(不使用
sh
时),PATH
变量可能不包含/bin
.
例如,cron
默认为非常严格的/usr/bin:/bin
PATH
,这当然很好,但其他上下文/环境可能由于某种特殊原因不包括/bin
。 - 当用户搞砸了他的
PATH
,这在初学者中很常见。 - 例如,当您调用调用 bash 脚本的 suid 程序时,为了安全起见。您不希望通过完全在用户控制下的
PATH
变量找到解释器!
最后,有人可能会争辩说,有一个合法的用例是env
生成bash
:当需要使用 .
将额外的环境变量传递给解释器时#!/usr/bin/env -S VAR=value bash
但这与bash
无关,因为当你控制 shebang 时,你也控制了整个脚本, 因此,只需在脚本中添加VAR=value
,即可避免使用 bash 脚本env
引入的上述问题。
我更喜欢将主程序包装在如下所示的脚本中,以检查系统上所有可用的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 "$@"
通常#!path/to/command会在执行时触发 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 - 在修改后的环境中运行程序
因此,env
将在其PATH
环境变量中查找命令bash
,并在单独的环境中执行,在该环境中,环境值可以传递给env
,如NAME=VALUE
对。
您可以使用不同的解释器(如 python 等)使用其他脚本对此进行测试。
#!/usr/bin/env python
# python commands