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 中添加一行以进行设置$PATH(man 5 crontab详细信息)。
Kevin 的评论指出 Pythonvirtualenv创建了一个特殊情况,其中环境在一个特殊目录中安装了一个 Python 解释器,该目录插入到$PATH. 对于那个特定的环境(可能还有其他人喜欢它),#!/usr/bin/env python技巧(或python3?)可能是最好的解决方案。(我自己没有使用过 virtualenv。)
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首选使用硬编码路径的唯一原因。
F1L*_*nux 61
在确定是使用绝对式还是逻辑式(/usr/bin/env在she-bang中是路径 )路径到解释器时,有(2)个关键考虑因素:
A)的解释可以在目标系统上找到
B)的正确版本的解释可以在目标系统上找到
如果我们同意“ b) ”是可取的,我们也同意:
c) 最好我们的脚本失败而不是使用不正确的解释器版本执行并可能获得不一致的结果。
如果我们不同意“ b) ”很重要,那么找到的任何口译员就足够了。
由于在 she-bang 中使用逻辑路径/usr/bin/env到解释器是最可扩展的解决方案,允许相同的脚本在具有相同解释器的不同路径的目标主机上成功执行,我们将使用 Python 进行测试,因为它的流行- 看看它是否符合我们的标准。
/usr/bin/env位于流行(不是“每个”)操作系统上的可预测、一致的位置?是:#!/usr/bin/env pythonX.x
import sys
print(sys.version)
print('Hello, world!')
Run Code Online (Sandbox Code Playgroud)
#!/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预期结果:即print(sys.version)= env pythonX.x。每次./test1.py使用不同的已安装 Python 版本执行时,都会打印 she-bang 中指定的正确版本。
测试注意事项:
/usr/bin尽管#!/usr/bin/env python将使用它在用户路径中找到的第一个 Python 版本是正确的,但我们可以通过指定一个版本号来缓和这种行为,例如#!/usr/bin/env pythonX.x. 事实上,开发人员并不关心“首先”找到哪个解释器,他们关心的是他们的代码是使用指定的解释器执行的,他们知道与他们的代码兼容以确保一致的结果——无论在文件系统中的任何地方。 ..
在可移植性/灵活性方面,使用逻辑- /usr/bin/env- 而不是绝对路径不仅满足我对不同版本Python 的测试的要求 a), b) & c) ,而且还具有模糊逻辑查找相同版本的好处解释器,即使他们生活在不同操作系统的不同路径上。尽管大多数发行版都尊重 FHS,但并非所有人都这样做。
因此,如果二进制文件位于与shebang 中指定的绝对路径不同的绝对路径中,则脚本将失败,使用逻辑路径的同一脚本会成功,因为它会一直运行直到找到匹配项,从而提供更高的跨平台可靠性和可扩展性。
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
它没有解决指定解释器完整路径的问题,它只是将其移动到env.
env并不/usr/bin/env比bash保证 in/bin/bash或 python in更能保证在/usr/bin/python。
env 用解释器的名称(例如 bash 或 python)覆盖 ARGV[0]。
这可以防止您的脚本名称出现在例如ps输出中(或更改它出现的方式/位置),并且无法找到它,例如,ps -C scriptname.sh
[更新2016-06-04]
还有第三个问题:
改变你的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
Run Code Online (Sandbox Code Playgroud)
运行它,例如,change-shebang.sh python2.7 *.py或change-shebang.sh $HOME/bin/my-experimental-ruby *.rb
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 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。