为什么最好使用“#!/usr/bin/env NAME”而不是“#!/path/to/NAME”作为我的shebang?

The*_*o61 588 shell-script shebang

我注意到我从其他人那里获得的一些脚本有 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比第二个更受欢迎?

Kei*_*son 540

它不一定更好。

的优点#!/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在用户 shell中的默认目录中,那么这个/usr/bin/env技巧就行不通了。您可以指定确切的路径,也可以在您的 crontab 中添加一行以进行设置$PATHman 5 crontab详细信息)。

Kevin 的评论指出 Pythonvirtualenv创建了一个特殊情况,其中环境在一个特殊目录中安装了一个 Python 解释器,该目录插入到$PATH. 对于那个特定的环境(可能还有其他人喜欢它),#!/usr/bin/env python技巧(或python3?)可能是最好的解决方案。(我自己没有使用过 virtualenv。)

  • 值得注意的是,如果你想使用特定的解释器版本, /usr/bin/env 还是更好的。仅仅是因为您的机器上通常安装了多个名为 perl5、perl5.12、perl5.10、python3.3、python3.32 等的解释器版本,并且如果您的应用程序仅在该特定版本上进行过测试,您仍然可以指定#!/usr/bin/env perl5.12 并且即使用户将它安装在不寻常的地方也没关系。根据我的经验,“python”通常只是系统标准版本(不一定是系统上的最新版本)的符号链接。 (9认同)
  • @root:对于 Perl,`use v5.12;` 服务于*某些*目的。如果系统有 Perl 5.14 而不是 5.12,`#!/usr/bin/env perl5.12` 将会失败。对于 Python 2 vs. 3,`#!/usr/bin/python2` 和 `#!/usr/bin/python3` 可能会起作用。 (7认同)
  • 如果 /usr/bin/perl 是 perl 5.8,$HOME/bin/perl 是 5.12,并且脚本需要 5.12 硬编码 /usr/bin/perl 在 shebangs 中,那么运行脚本可能会很痛苦。我很少看到 /usr/bin/env perl 从 PATH 中抓取 perl 是一个问题,但它通常非常有帮助。而且它比 exec hack 漂亮得多! (6认同)
  • 如果它不能与`/usr/bin/perl` 一起工作,我会很快发现,并且保持它是最新的是系统所有者/管理员的责任。如果你想用你自己的 perl 运行我的脚本,可以随意获取和修改一个副本,或者通过 `perl foo` 调用它。(并且您可能会考虑支持此答案的 55 个人也知道一两件事的可能性。您肯定是对的,但他们都错了,但这不是我敢打赌的方式。) (5认同)
  • 这个答案与特别是 Python 的常用方式不一致。您要使用的 Python 可执行文件*很少*`/usr/bin/python`。通常,您想要的位于 [Virtualenv](https://virtualenv.pypa.io/en/latest/) 中,并且位于用户 `$PATH` 的前面。在极少数情况下*不是*情况,无论如何`/usr/bin`通常会在用户的`$PATH`上。 (4认同)
  • @GoodPerson:假设我编写了一个要安装在 `/usr/local/bin` 中的 Perl 脚本。我碰巧知道它与`/usr/bin/perl` 一起工作正常。我*不知道*它是否适用于某些随机用户碰巧在他或她的“$PATH”中的任何“perl”可执行文件。也许有人正在试验一些古老的 Perl 版本;因为我指定了 `#!/usr/bin/perl`,我的脚本(用户甚至不一定知道或关心它是 Perl 脚本)不会停止工作。 (3认同)
  • 不鼓励@thst `#!bash`,因为它不起作用。处理 `#!` 行的机制不使用 `$PATH`。(是的,`env` 技巧很容易受到攻击——这可以通过在你的 `$PATH` 中不包含 `.` 来最小化。) (3认同)
  • 这个答案的推理太糟糕了,我什至不知道从哪里开始。当然,您要尊重用户的 PATH!。您不可能知道要使用哪个解释器。 (2认同)
  • 您也不知道它是否适用于 /usr/bin/perl 中的 perl - 它可能是不同的版本。如果 /usr/bin/perl 中的 perl 版本更新了怎么办?如果用户甚至没有 /usr/bin/perl(而更有可能拥有 /usr/local/bin/perl)怎么办。如果 /usr/bin 中的 perl 版本完全损坏,而用户在 ~/opt/bin 中有修补版本怎么办?尊重用户的 PATH 更正确。如果你为一个非常具体的永不改变的系统编写代码,我可以看到你依赖系统的怪癖。否则最好是便携的 (2认同)

Tim*_*edy 116

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

#!/usr/local/bin/python
Run Code Online (Sandbox Code Playgroud)

如果 python 安装在 /usr/local/bin 中,则只会运行您的脚本。

#!/usr/bin/env python
Run Code Online (Sandbox Code Playgroud)

将解释您的$PATH,并在您的$PATH.

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

可移植性是env首选使用硬编码路径的唯一原因。

  • 随着 `virtualenv` 使用量的增加,`python` 可执行文件的自定义目录特别常见。 (26认同)
  • `#!不使用 python 是因为您必须与 python 二进制文件位于同一目录中,因为裸字 python 被解释为文件的完整路径。如果当前目录中没有 python 二进制文件,则会收到类似“bash: ./script.py: python: bad interpreter: No such file or directory”的错误。这与您使用 `#! /not/a/real/path/python` (9认同)
  • 怎么样`#!python`,为什么不使用? (2认同)
  • @TimKennedy `uname -a` 说:Linux silvius 5.6.0-2-amd64 #1 SMP Debian 5.6.14-1 (2020-05-23) x86_64 GNU/Linux,我正在使用 zsh。zsh 是关键:从 bash 运行或使用 `perl -e 'exec "/tmp/foo" or die $!'` 时,我也遇到同样的失败。到目前为止,zsh 正在做比它必须做的更多的工作。 (2认同)
  • @TimKennedy aha,来自 `man zsh-misc`:“如果由于文件不是可执行格式且文件不是目录而执行失败,则假定它是一个 shell 脚本。 /bin/sh 被生成来执行如果程序是以‘#!’开头的文件,则第一行的其余部分指定该程序的解释器。shell 将在内核中不处理此可执行格式的操作系统上执行指定的解释器。” 因此,zsh 实现了自己的 shebang-line 解析,以防它在非 POSIX 系统上运行,并且其实现与 Linux 内核的不同。 (2认同)

F1L*_*nux 61

目标标准/要求:

在确定是使用绝对式还是逻辑式(/usr/bin/env在she-bang中是路径 )路径到解释器时,有(2)个关键考虑因素:

A)解释可以在目标系统上找到

B)正确版本解释可以在目标系统上找到

如果我们同意b) ”是可取的,我们也同意

c) 最好我们的脚本失败而不是使用不正确的解释器版本执行并可能获得不一致的结果。

如果我们不同意b) ”很重要,那么找到的任何口译员就足够了。

测试:

由于在 she-bang 中使用逻辑路径/usr/bin/env到解释器是最可扩展的解决方案,允许相同的脚本在具有相同解释器的不同路径的目标主机上成功执行,我们将使用 Python 进行测试,因为它的流行- 看看它是否符合我们的标准。

  1. 是否/usr/bin/env位于流行(不是“每个”)操作系统上的可预测、一致的位置?
  • RHEL 7.5
  • Ubuntu 18.04
  • 树莓派 10(“克星”)
  • OSX 10.15.02
  1. 下面的 Python 脚本在测试期间在虚拟信封(使用Pipenv)的内部和外部执行:
    #!/usr/bin/env pythonX.x
    import sys
    print(sys.version)
    print('Hello, world!')
    
    Run Code Online (Sandbox Code Playgroud)
  2. 脚本中的 she-bang 由所需的 Python 版本号切换(全部安装在同一主机上):
  • #!/usr/bin/env python2
  • #!/usr/bin/env python2.7
  • #!/usr/bin/env python3
  • #!/usr/bin/env python3.5
  • #!/usr/bin/env python3.6
  • #!/usr/bin/env python3.7
  1. 预期结果:即print(sys.version)= env pythonX.x。每次./test1.py使用不同的已安装 Python 版本执行时,都会打印 she-bang 中指定的正确版本。

  2. 测试注意事项:

  • 测试仅限于 Python
  • Perl:像 Python 一样 -根据 FHS必须存在/usr/bin
  • 我还没有在每个可能的 Linuxy/Unixy 操作系统和每个操作系统版本上尝试所有可能的组合。

结论:

尽管#!/usr/bin/env python将使用它在用户路径中找到的第一个 Python 版本是正确的,但我们可以通过指定一个版本号来缓和这种行为,例如#!/usr/bin/env pythonX.x. 事实上,开发人员并不关心“首先”找到哪个解释器,他们关心的是他们的代码是使用指定的解释器执行的,他们知道与他们的代码兼容以确保一致的结果——无论在文件系统中的任何地方。 ..

在可移植性/灵活性方面,使用逻辑- /usr/bin/env- 而不是绝对路径不仅满足我对不同版本Python 的测试的要求 a), b) & c) ,而且还具有模糊逻辑查找相同版本的好处解释器,即使他们生活在不同操作系统的不同路径上。尽管大多数发行版都尊重 FHS,但并非所有人都这样做

因此,如果二进制文件位于与shebang 中指定的绝对路径不同的绝对路径中,则脚本将失败,使用逻辑路径的同一脚本会成功,因为它会一直运行直到找到匹配项,从而提供更高的跨平台可靠性和可扩展性。

  • 优秀的分析。我们很感激。 (7认同)
  • **UPVOTED** 仅针对 **_formatting_**,不要介意_非常_ **有用的** 答案。 (4认同)
  • 这个问题不是针对 Python 的。我认为您不能安全地假设其他解释器将被安装为例如`interpX` *和* `interpX.x`。例如,我目前使用的系统有“perl”、“perl5.26.3”和“perl5.30.1”。另一个有 `perl` 和 `perl5.26.1`。两者都没有 `perl5` 命令。此外,您没有提到您测试的任何系统上的 `pythonX` 和 `pythonX.x` 解释器的安装位置。如果它们都在 `/usr/bin` 中,那么你的实验并没有证明 `#!/usr/bin/env pythonX.x` 比 `#!/usr/bin/pythonX.x` 有任何优势。 (3认同)
  • 请注意,并非每个 Linux 系统都符合 FHS,远非如此。而且,套用一句老话,“并非全世界都是 Linux”。许多 Unix(y) 系统将 Python、Perl,甚至 bash 或 tcsh 作为“可选的、不受支持的第三方添加”,大概在`/usr/local` 或 `/opt/*packagename*` 或一些非常奇特的位置。 (2认同)

Gil*_*il' 53

在给定系统上指定绝对路径更精确。缺点是太精确了。假设您意识到 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正是因为它在shebangs 中的广泛使用。/usr/bin/env是您可以信赖的东西。

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

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

#!/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 …
Run Code Online (Sandbox Code Playgroud)


DrH*_*yde 26

特别是对于 perl,使用#!/usr/bin/env是一个坏主意,原因有两个。

首先,它不便携。在一些晦涩的平台上,env 不在 /usr/bin 中。其次,正如Keith Thompson所指出的,它可能会导致在 shebang 行上传递参数时出现问题。最便携的解决方案是这样的:

#!/bin/sh
exec perl -x "$0" "$@"
#!perl
Run Code Online (Sandbox Code Playgroud)

有关其工作原理的详细信息,请参阅“perldoc perlrun”及其关于 -x 参数的说明。


小智 19

两者之间存在区别的原因在于脚本的执行方式。

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

#!bash

echo 'foo'
Run Code Online (Sandbox Code Playgroud)

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

Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.
Run Code Online (Sandbox Code Playgroud)

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


cas*_*cas 15

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

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

    env并不/usr/bin/envbash保证 in/bin/bash或 python in更能保证在/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,这就像将光标移动到#!您想要的行一样简单,然后键入dd1GPY1GP。即使使用像 那样微不足道的编辑器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
Run Code Online (Sandbox Code Playgroud)

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

  • 这正是 env 帮助避免的:取决于与 python 或其他无关的系统管理员或操作系统特定的技术知识。 (6认同)
  • 这里没有其他人提到 ARGV[0] 问题。并且没有人以直接解决使用它的参数之一的形式提到 /path/to/env 问题(即 bash 或 perl 可能位于意外位置)。 (5认同)
  • 解释器路径问题很容易由系统管理员使用符号链接修复,或者由用户编辑他们的脚本。这当然不是一个足够重要的问题来鼓励人们忍受在此处和其他问题上提到的 shebang 行上使用 env 引起的所有其他问题。这与鼓励`csh` 脚本促进糟糕的解决方案的方式相同:它是有效的,但有更好的替代方案。 (3认同)
  • 非系统管理员可以要求他们的系统管理员来做。或者他们可以简单地编辑脚本并更改`#!` 行。 (3认同)
  • 不是每个人都是他们机器上的系统管理员。`env` 解决方案旨在帮助非操作系统专家复制/粘贴或下载很有可能按原样工作的脚本。它也是允许更高级的用户通过仅更改其 PATH / PATH 顺序(对不起,太长的句子......) (2认同)
  • 我希望我能投票一千次,因为这实际上是一个很好的答案_无论哪种方式_——如果有人使用 `/usr/bin/env` 并且我需要在本地摆脱它,或者如果他们没有使用它并且我需要添加它。这两种情况都可能发生,因此本答案中提供的脚本是工具箱中一个潜在有用的工具。 (2认同)
  • 我希望我能投票一千次来演示 ed(1) 在管道中的使用! (2认同)

Not*_*Now 12

在此处添加另一个示例:

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

在 cmd 行上运行它,显示#!/usr/bin/env ruby在脚本中使用时将使用哪个 ruby​​ 版本 :

env ruby --version

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


小智 7

如果您纯粹是为您自己或您的工作而写作,并且您所呼叫的口译员的位置总是在同一个地方,请务必使用直接路径。在所有其他情况下使用#!/usr/bin/env.

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

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

  • “在所有其他情况下使用 #!/usr/bin/env”太强了。// 正如@KeithThompson 和其他人指出的那样,#!/usr/bin/env 意味着脚本可以根据运行的人/方式而不同。有时,这种不同的行为可能是一个安全漏洞。// 例如,永远不要对 setuid 或 setgid 脚本使用“#!/usr/bin/env python”,因为调用脚本的用户可以将恶意软件放在 PATH 中名为“python”的文件中。setuid/gid 脚本是否是一个好主意是一个不同的问题 - 但当然,任何 setuid/gid 可执行文件都不应该信任用户提供的环境。 (7认同)
  • @KrazyGlew,您无法在 Linux 上设置脚本的 uid 。您可以通过可执行文件来完成此操作。在编写此可执行文件时,清除环境变量是一种很好的做法,并且已被广泛采用。一些环境变量也被[故意忽略](https://unix.stackexchange.com/a/48549/176127)。 (3认同)

Mir*_*ner 5

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

#!/usr/bin/env bash
Run Code Online (Sandbox Code Playgroud)

代替

#!/usr/bin/bash
Run Code Online (Sandbox Code Playgroud)

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

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

如果你想要一个可移植的 bash/csh/perl/whatever 脚本,它可以在大多数 Linux 发行版和 FreeBSD 下运行,你应该使用#!/usr/bin/env

同时注意到,大多数Linux安装都还(硬)链接应该的ENV二进制文件为/ bin / env的或softlinked在/ usr / bin中成/ bin中没有的家当被使用。所以不要使用#!/bin/env