如果使用
#!/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..."
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语法的原因。
#!/usr/bin/bash
可能无法正常工作。在我的系统(Ubuntu 17.04)上,bash
是/bin/bash
,而没有/usr/bin/bash
。/bin
和/usr/bin
之间的区别在很大程度上是随意的。另外,大多数系统将env
命令放在/usr/bin
中,但这并不保证。 - Keith Thompson