为什么最好用“#!/ usr / bin / env名称”代替“#!/ path / to / NAME”作为我的shebang?


458

我注意到,我从其他人那里获得的一些脚本具有shebang,#!/path/to/NAME而另一些(使用相同工具NAME)具有shebang #!/usr/bin/env NAME

两者似乎都能正常工作。在教程中(例如,在Python上),似乎有人建议后者shebang更好。但是,我不太明白为什么会这样。

我意识到,要使用后一个shebang,NAME必须在PATH中,而第一个shebang没有此限制。

另外,对我来说,第一个似乎是更好的shebang,因为它精确指定了NAME的位置。因此,在这种情况下,如果存在多个版本的NAME(例如/ usr / bin / NAME,/ usr / local / bin / NAME),则第一种情况指定要使用的版本。

我的问题是,为什么第一个shebang优先于第二个?


12

@ TheGeeko61:就我而言,我的东西坏了,有些变量不在env中。因此,我建议使用此shebang来验证是否正确加载了env。
Gigamegs 2012年

Answers:


462

不一定更好。

这样做的好处#!/usr/bin/env python是,它将使用python用户的中第一个出现的可执行文件$PATH

不利#!/usr/bin/env python是,它会使用任何python可执行文件第一次出现在用户的$PATH

这意味着脚本的行为可能取决于运行它的人。对于一个用户,它可能会使用/usr/bin/python与操作系统一起安装的。另外,它可能会使用/home/phred/bin/python无法完全正常运行的实验。

如果python仅安装在/usr/local/bin,谁没有一个用户/usr/local/bin$PATH甚至不能够运行该脚本。(这在现代系统上可能不太可能,但是对于比较晦涩的解释器来说,很容易发生。)

通过指定,#!/usr/bin/python您可以确切指定将使用哪个解释程序在特定系统上运行脚本。

另一个潜在的问题是该#!/usr/bin/env技巧不允许您将参数传递给解释器(脚本名称除外,该名称是隐式传递的)。这通常不是问题,但是可以。许多Perl脚本都是用编写的#!/usr/bin/perl -w,但如今use warnings;建议将其替换。Csh脚本应使用#!/bin/csh -f-但首先不建议使用csh脚本。但是可能还有其他例子。

在新系统上设置帐户时,我在安装的个人源代码控制系统中有许多Perl脚本。我使用安装程序脚本来修改#!每个脚本的行,因为该脚本会将其安装到我的计算机中$HOME/bin。(除了#!/usr/bin/perl最近我不需要使用其他任何东西;这可以追溯到默认情况下通常不安装Perl的时代。)

一点要点:#!/usr/bin/env窍门可以说是对env命令的滥用,其最初意图(顾名思义)是在环境改变的情况下调用命令。此外,某些较旧的系统(如果我没记错的话,包括SunOS 4)在中没有env命令/usr/bin。这些都不大可能成为重大问题。 env确实以这种方式工作,许多脚本确实使用了该#!/usr/bin/env技巧,并且操作系统提供者不太可能做任何破坏它的事情。它可能如果你希望你的脚本一个很老的系统上运行,但随后你可能总有需要修改它是一个问题。

另一个可能的问题(由于Sopalajo de Arrierez在评论中指出了这一点)是cron作业在受限的环境中运行。特别是,$PATH通常是类似的东西/usr/bin:/bin。因此,即使包含解释器的目录恰好不在这些目录之一中,即使该目录$PATH位于用户外壳程序的默认目录中,该/usr/bin/env技巧也无法奏效。您可以指定确切的路径,也可以在crontab中添加一行以进行设置$PATHman 5 crontab有关详细信息)。


4
如果/ usr / bin / perl是perl 5.8,$ HOME / bin / perl是5.12,并且脚本需要在Shebang中使用5.12硬编码/ usr / bin / perl,则运行该脚本可能会很麻烦。我很少见过让/ usr / bin / env perl从PATH中获取perl是一个问题,但是它通常非常有帮助。而且比执行黑客更漂亮!
威廉·珀塞尔

8
值得注意的是,如果要使用特定的解释器版本,/ usr / bin / env仍然更好。原因很简单,通常安装在机器的perl5命名,perl5.12,perl5.10,python3.3,python3.32等上,如果你的应用程序只被该特定版本测试多个版本的解释,你还可以指定#!/ usr / bin / env perl5.12,即使用户将它安装在不寻常的地方也可以。以我的经验,“ python”通常只是系统标准版本(不一定是系统上的最新版本)的符号链接。

6
@root:对Perl,use v5.12;提供一些为此而作的。#!/usr/bin/env perl5.12如果系统具有Perl 5.14但不具有5.12 ,它将失败。适用于Python 2 vs. 3,#!/usr/bin/python2并且#!/usr/bin/python3可能有效。
基思·汤普森

3
@GoodPerson:假设我写了一个Perl脚本安装在中/usr/local/bin。我碰巧知道它可以正常使用/usr/bin/perl。我不知道它是否可以与perl某些随机用户碰巧拥有的可执行文件一起使用$PATH。也许有人在尝试一些古老的Perl版本;因为我指定了#!/usr/bin/perl,所以我的脚本(用户甚至不必知道或关心的是Perl脚本)都不会停止工作。
基思·汤普森

5
如果它不起作用/usr/bin/perl,我会很快找到,并且保持其最新状态是系统所有者/管理员的责任。如果要使用自己的perl运行我的脚本,请随时获取和修改副本或通过调用它perl foo。(而且您可能会考虑赞成该答案的55个人也知道一两件事的可能性。您肯定是对的,他们全都错了,但这不是我敢打赌的方式。)
Keith汤普森

101

因为/ usr / bin / env可以解释您的$PATH,这使脚本更易于移植。

#!/usr/local/bin/python

仅当在/ usr / local / bin中安装了python时,才会运行脚本。

#!/usr/bin/env python

将解释您的内容$PATH,并在您目录中的任何目录中找到python $PATH

所以,你的脚本更便于携带,且无需在哪里蟒蛇被安装为系统的修改就可以/usr/bin/python,或者/usr/local/bin/python,甚至是自定义目录(已加入$PATH),喜欢/opt/local/bin/python

可移植性是使用env优先于硬编码路径的唯一原因。


19
python随着virtualenv使用量的增加,可执行文件的自定义目录尤其常见。
Xiong Chiamiov

1
怎么样 #! python,为什么不使用呢?
kristianp '18

6
#! python未使用,因为您必须将python二进制文件放在同一目录中,因为裸字python被解释为文件的完整路径。如果当前目录中没有python二进制文件,则会出现类似的错误bash: ./script.py: python: bad interpreter: No such file or directory。就像您使用时一样#! /not/a/real/path/python
Tim Kennedy

47

在给定的系统上,指定绝对路径更为精确。缺点是它太精确了。假设您意识到Perl的系统安装对于您的脚本来说太旧了,而您想使用自己的脚本:那么您必须编辑脚本并更改#!/usr/bin/perl#!/home/myname/bin/perl。更糟糕的是,如果您/usr/bin在某些计算机/usr/local/bin上,/home/myname/bin/perl其他计算机上以及其他计算机上都安装了Perl ,则必须维护三个单独的脚本副本,并在每台计算机上执行相应的脚本。

#!/usr/bin/env如果PATH不好则中断,但几乎所有操作都会中断。尝试以错误的PATH方式进行操作非常有用,并且表明您对脚本所运行的系统了解甚少,因此无论如何您都不能依赖任何绝对路径。

您几乎可以依靠两个程序来定位两个unix变量:/bin/sh/usr/bin/env。某些晦涩且大多已淘汰的Unix变体/bin/env没有/usr/bin/env,但是您不太可能遇到它们。/usr/bin/env正是由于它在shebang中的广泛使用,才有了现代系统。/usr/bin/env您可以依靠。

除了之外/bin/sh,您唯一应该在shebang中使用绝对路径的时间是脚本不是可移植的,因此您可以依靠解释器的已知位置。例如,只能在Linux上运行的bash脚本可以安全使用#!/bin/bash。仅打算在内部使用的脚本可以依赖于内部解释器位置约定。

#!/usr/bin/env确实有缺点。它比指定绝对路径更灵活,但仍然需要知道解释器名称。有时您可能想运行不在中的解释器,$PATH例如,在相对于脚本的位置中。在这种情况下,您通常可以制作一个可以由标准外壳程序和所需解释程序解释的多语言脚本。例如,要使Python 2脚本既可以移植到pythonPython 3和python2Python 2的系统,又可以移植到pythonPython 2和python2不存在的系统:

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0" "$@"
else
  exec python "$0" "$@"
fi
'''
# real Python script starts here
def …

22

专门针对perl,使用#!/usr/bin/env有一个坏主意,原因有两个。

首先,它不是便携式的。在某些晦涩的平台上,env不在/ usr / bin中。其次,正如基思·汤普森Keith Thompson)所指出的那样,在shebang线上传递论点可能会造成麻烦。最大的可移植解决方案是这样的:

#!/bin/sh
exec perl -x "$0" "$@"
#!perl

有关其工作方式的详细信息,请参见“ perldoc perlrun”以及有关-x参数的说明。


正是我正在寻找的分析服务商:如何为perl脚本编写可移植的“ shebang”,该脚本将允许将更多的资产传递给perl(示例中的最后一行接受其他的资产)。
AmokHuginnsson,2015年

15

两者之间有区别的原因是因为脚本的执行方式。

需要使用/usr/bin/env(如其他答案中所述,/usr/bin某些操作系统上没有此功能),因为您不能只是在可执行文件名的后面放置可执行文件名#!-它必须是绝对路径。这是因为该#!机制在比Shell更低的级别上起作用。它是内核二进制加载程序的一部分。可以测试。将其放在文件中并标记为可执行文件:

#!bash

echo 'foo'

当您尝试运行它时,您会发现它打印出如下错误:

Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.

如果文件被标记为可执行文件并以开头#!,则内核(不知道$PATH当前目录或当前目录:这是用户访问的概念)将使用绝对路径查找文件。因为使用绝对路径存在问题(如其他答案中所述),所以有人想出了一个窍门:您可以运行/usr/bin/env(几乎总是在该位置)运行$PATH


11

使用还有两个问题 #!/usr/bin/env

  1. 它不能解决为解释器指定完整路径的问题,而只是将其移动到env

    env是没有更多的保证是在/usr/bin/envbash保证是在/bin/bash或在python /usr/bin/python

  2. env 用解释器的名称(例如bash或python)覆盖ARGV [0]。

    这样可以防止您的脚本名称出现在例如ps输出(或更改其显示方式/位置)中,并且无法通过以下方式找到它:ps -C scriptname.sh

[更新2016-06-04]

第三个问题:

  1. 更改PATH不仅是编辑脚本的第一行,还需要更多的工作,尤其是在编写脚本时,这种编辑很简单。例如:

    printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py

    将目录追加或预添加到$ PATH非常容易(尽管您仍然必须编辑文件以使其永久化-您~/.profile或其他人-而且编写脚本远非易事,因为PATH可以在脚本中的任何位置进行设置,不在第一行)。

    更改PATH目录的顺序要困难得多#!

    而且您仍然遇到使用#!/usr/bin/env带来的所有其他问题。

    @jlliagre在评论中建议#!/usr/bin/env“通过仅更改其PATH / PATH顺序”对测试具有多个解释器版本的脚本很有用。

    如果您需要这样做,则#!在脚本顶部仅添加几行(除了第一行之外,它们只是在任何地方的注释),然后剪切/复制并粘贴您现在想要使用的那一行要容易得多。第一行。

    在中vi,这就像将光标移动到所需的#!行,然后输入dd1GP或一样简单Y1GP。即使使用的编辑器nano这么简单,使用鼠标进行复制和粘贴也要花费几秒钟。


总的来说,使用的优势#!/usr/bin/env充其量是最小的,甚至肯定无法抵消其劣势。甚至“便利”优势在很大程度上也是虚幻的。

IMO,这是一种特殊的程序员提倡的一个愚蠢的想法,他认为操作系统不是要使用的东西,而是要解决的问题(或者最好是忽略不计)。

PS:这是一个简单的脚本,可一次更改多个文件的解释器。

change-shebang.sh

#!/bin/bash

interpreter="$1"
shift

if [ -z "$(type -P $interpreter)" ] ; then
  echo "Error: '$interpreter' is not executable." >&2
  exit 1
fi

if [ ! -d "$interpreter" ] && [ -x "$interpreter" ] ; then
  shebang='#!'"$(realpath -e $interpreter)" || exit 1
else
  shebang='#!'"$(type -P $interpreter)"
fi

for f in "$@" ; do
  printf "%s\n" 1 i "$shebang" . w | ed "$f"
done

运行它,例如,change-shebang.sh python2.7 *.pychange-shebang.sh $HOME/bin/my-experimental-ruby *.rb


4
这里没有其他人提到ARGV [0]问题。而且没有人提到过/ path / to / env问题,其形式直接解决了使用它的一个参数(即bash或perl可能位于意外位置)。
cas

2
解释程序路径问题可以通过具有符号链接的sysadmin或用户编辑其脚本的方式轻松解决。鼓励人们忍受在shebang行上使用env引起的所有其他问题,当然这不是一个足够重大的问题,在本文和其他问题上已经提到。以一种鼓励csh脚本编写的方式来推广一种不好的解决方案的方式来推广一种不好的解决方案:这是可行的,但还有更好的选择。
cas 2015年

3
非系统管理员可以要求其系统管理员执行此操作。或者他们可以简单地编辑脚本并更改#!行。
cas 2015年

2
这正是env有助于避免的事情:取决于与python等无关的sysadmin或OS特定的技术知识。
jlliagre

1
我希望我能给予好评此一千倍,因为它实际上是一个伟大的答案无论哪种方式 -如果有人使用/usr/bin/env,我需要得到当地摆脱它,或者,如果他们没有使用它,我需要添加它。两种情况都可能发生,因此此答案中提供的脚本是在工具箱中具有潜在有用的工具。
jstine

8

在此处添加另一个示例:

例如,env当您想在多个rvm环境之间共享脚本时,使用也很有用。

在cmd行上运行此命令,显示#!/usr/bin/env ruby在脚本内使用时将使用哪个Ruby版本 :

env ruby --version

因此,当您使用时env,可以通过rvm使用不同的ruby版本,而无需更改脚本。


1
//,好主意。多重解释的整点不是破解密码,或有代码依赖于 具体 解释
弥敦道(Nathan Basanese)'16

4

如果您纯粹是为自己或工作而写,而您要呼叫的口译员的位置始终在同一地方,则一定要使用直接路径。在所有其他情况下使用#!/usr/bin/env

原因如下:在您的情况下,python无论您使用哪种语法,解释器都位于同一位置,但对于许多人来说,它可能已安装在其他位置。尽管大多数主要的程序解释器都位于/usr/bin/许多较新的软件中,但默认情况下为/usr/local/bin/

我什至会争辩说总是使用它,#!/usr/bin/env因为如果您安装了同一个解释器的多个版本,并且您不知道您的Shell将默认使用什么版本,那么您应该修复该问题。


5
“在所有其他情况下都使用#!/ usr / bin / env”太强了。//正如@KeithThompson和其他人指出的那样,#!/ usr / bin / env表示脚本的运行方式可能有所不同,具体取决于运行者/运行方式。有时,这种不同的行为可能是安全漏洞。//例如,永远不要对setuid或setgid脚本使用“#!/ usr / bin / env python”,因为调用该脚本的用户可以将恶意软件放置在PATH中名为“ python”的文件中。setuid / gid脚本是否曾经是一个好主意,这是一个不同的问题-但可以肯定的是,任何setuid / gid可执行文件都不应该信任用户提供的环境。
Krazy Glew

@KrazyGlew,您不能在Linux上设置脚本。您通过可执行文件进行操作。而且,在编写此可执行文件时,清除环境变量是一种很好的做法,而且已广泛完成。一些环境变量也被有意地忽略了
MayeulC '18 -10-7

3

出于便携性和兼容性的原因,最好使用

#!/usr/bin/env bash

代替

#!/usr/bin/bash

有很多可能,二进制文件可能位于Linux / Unix系统上。查看hier(7)联机帮助页以获取有关文件系统层次结构的详细说明。

例如,FreeBSD将所有软件(不是基本系统的一部分)安装在/ usr / local /中。由于bash不属于基本系统,因此bash二进制文件安装在/ usr / local / bin / bash上

如果您想在大多数Linux发行版和FreeBSD下运行可移植的bash / csh / perl /任何脚本,则应使用#!/ usr / bin / env

还请注意,大多数Linux安装程序也已将env二进制文件(硬)链接到/ bin / env或将/ usr / bin软链接到/ bin,但在shebang中应该使用。所以不要使用#!/ bin / env

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.