在bash
脚本的标题中,这两个语句之间的区别是什么?
#!/usr/bin/env bash
#!/usr/bin/bash
当我试图看到env
手册页时,我得到了这个定义:
env - run a program in a modified environment
这是什么意思?
通过/usr/bin/env
运行命令的好处是可以查找当前环境中程序的默认版本。
这样,您不必在系统上的特定位置查找它,因为这些路径可能位于不同系统上的不同位置。只要它在你的道路上,它就会找到它。
一个缺点是如果你想支持Linux,你将无法传递多个参数(例如你将无法编写/usr/bin/env awk -f
),如POSIX is vague关于如何解释这一行,并且Linux在第一个空格之后解释所有内容表示单个参数。你可以在某些版本的/usr/bin/env -S
上使用env
来解决这个问题,但是这个脚本将变得更不便携,并且在相当近期的系统上会中断(例如,如果不是更晚的话,甚至是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,而不必重做可执行行)。但是在以安全性为重点的情况下,后者将是首选,因为它限制了代码注入的可能性。
使用#!/usr/bin/env NAME
使shell在$ PATH环境变量中搜索NAME的第一个匹配项。如果您不知道绝对路径或不想搜索它,它会很有用。
通过使用env命令,不是像在/usr/bin/bash/
中那样明确定义解释器的路径,而是从首次找到解释器的任何地方搜索并启动解释器。这有两个upsides and downsides
我发现它很有用,因为当我不知道env时,在我开始编写脚本之前我就是这样做的:
type nodejs > scriptname.js #or any other environment
然后我将文件中的那一行修改为shebang。
我这样做,因为我并不总是记得我的计算机上的nodejs在哪里 - / usr / bin /或/ bin /,所以对我来说env
非常有用。也许这里有细节,但这是我的理由
如果shell脚本以#!/bin/bash
开头,它们将始终使用bash
中的/bin
运行。然而,如果他们从#!/usr/bin/env bash
开始,他们将在bash
中搜索$PATH
,然后从他们能找到的第一个开始。
为什么这会有用?假设你想运行需要bash 4.x或更新版本的bash
脚本,但你的系统只安装了bash
3.x,目前你的发行版没有提供更新的版本,或者你不是管理员而且无法更改安装的内容那个系统。
当然,您可以从头开始下载bash源代码并构建自己的bash,例如将其放置到~/bin
。你也可以在你的$PATH
文件中修改你的.bash_profile
变量,将~/bin
作为第一个条目(PATH=$HOME/bin:$PATH
作为~
不会在$PATH
中展开)。如果你现在调用bash
,shell将首先在$PATH
中按顺序查找它,所以它从~/bin
开始,在那里找到你的bash
。如果脚本使用bash
搜索#!/usr/bin/env 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
通常,经典的贝壳都应该位于/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语法。