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

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

我意识到,要使用后一个shebang,NAME必须在PATH中,而第一个shebang没有此限制。 br />
另外,对我来说,第一个似乎是更好的shebang,因为它精确地指定了NAME的位置。因此,在这种情况下,如果存在多个版本的NAME(例如/ usr / bin / NAME,/ usr / local / bin / NAME),则第一种情况指定要使用的版本。

我的问题是为什么第一个Shebang优于第二个Shebang?

评论

看到这个答案...

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

#1 楼

客观标准/要求:
在确定在爆炸中使用解释器的绝对路径还是逻辑路径(/usr/bin/env)时,有(2)个关键注意事项:
a)可以找到解释器在目标系统上
b)可以在目标系统上找到正确的解释器版本
如果我们同意“ b)是可取的,我们还同意:
c)我们的脚本更可取失败而不是使用错误的解释器版本执行,并且可能会导致不一致的结果。
如果我们不同意“ b)”的问题,那么找到的任何解释器就足够了。
测试:
自在she-bang中使用逻辑路径-到解释器的/usr/bin/env是最可扩展的解决方案,它允许相同的脚本在具有指向相同解释器的不同路径的目标主机上成功执行,我们将对其进行测试-由于Python的流行-查看其是否符合我们的标准。

/usr/bin/env是否居住在POPULAR(n还是“每个”)操作系统?是:


RHEL 7.5
Ubuntu 18.04
Raspbian 10(“ Buster”)
OSX 10.15.02


下面的Python脚本在测试过程中在虚拟信封的内部和外部(使用Pipenv)同时执行:
#!/usr/bin/env pythonX.x
import sys
print(sys.version)
print('Hello, world!')


脚本中的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



预期结果:print(sys.version) = env pythonX.x。每次使用不同的已安装Python版本执行./test1.py时,都会打印出she-bang中指定的正确版本。


测试注意事项:



测试仅限于Python
Perl:像Python一样-根据FHS,必须存在于/usr/bin

我尚未尝试在每种可能的Linuxy / Unixy操作系统和每种操作系统的版本上进行所有可能的组合。

结论:
虽然#!/usr/bin/env python将使用第一个版本的Linux是正确的它在用户的Path中找到的Python,我们可以通过指定版本号(例如#!/usr/bin/env pythonX.x)来缓和此行为。实际上,开发人员并不关心哪个解释器是“第一个”的,他们关心的只是使用知道的代码与指定的解释器执行代码,以确保结果一致(无论文件系统中可能存在的位置)。 ..
就可移植性/灵活性而言,使用逻辑/usr/bin/env-而非绝对路径不仅符合我在不同版本的Python上进行测试的要求a),b)和c),而且还具有以下优点:即使它们在不同操作系统上的不同路径中,它们也将使用模糊逻辑查找相同的版本解释器。而且尽管大多数发行版都尊重FHS,但并非所有发行版都如此。
因此,如果二进制文件位于随后在shebang中指定的不同绝对路径中,则脚本将失败,同一脚本将使用逻辑路径SUCCEEDS,直到发现匹配,从而提供跨平台的更高可靠性和可扩展性。

评论


出色的分析。我们对此表示赞赏。

–TheGeeko61
2月20日23:55

这个问题并非特定于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。

–基思·汤普森(Keith Thompson)
2月21日在2:55



请注意,并非每一个Linux系统都符合FHS,远不是那么。俗话说:“不是所有的世界都是Linux”。许多Unix(y)系统都将Python,Perl,甚至bash或tcsh作为“可选的,不受支持的第三方增补”,大概在/ usr / local或/ opt / * packagename *下或某些非常特殊的位置。

– vonbrand
2月23日4:01

仅支持格式化,不要介意非常有用的答案。

– Teemu Leisti
8月26日13:30



@TeemuLeisti我尝试让其他技术专家省去解决遇到的相同问题的精力。因此,很高兴听到有人发现我的解决方案很有用,尤其是当它涉及大量的测试和文档编制(例如此类)时。最有责任的你的话!

– F1Linux
8月26日18:22

#2 楼

它不一定更好。

#!/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脚本,当我在新系统上设置帐户时会安装这些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中添加一行以设置$PATH(有关详细信息,请参见man 5 crontab)。

评论


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

–威廉·珀塞尔(William Pursell)
2012年1月25日20:30

值得注意的是,如果要使用特定的解释器版本,/ usr / bin / env仍然更好。仅仅是因为您的计算机上通常安装了多个解释器版本,分别为perl5,perl5.12,perl5.10,python3.3,python3.32等,并且如果您的应用仅在该特定版本上进行了测试,则仍然可以指定#!/ usr / bin / env perl5.12,即使用户将它安装在不寻常的地方也可以。以我的经验,“ python”通常只是系统标准版本(不一定是系统上的最新版本)的符号链接。

– root
13年7月20日在19:44

@root:对于Perl,请使用v5.12;用于某些目的。如果系统具有Perl 5.14但没有5.12,则#!/ usr / bin / env perl5.12将失败。对于Python 2和3,#!/ usr / bin / python2和#!/ usr / bin / python3可能有效。

–基思·汤普森(Keith Thompson)
13年7月20日在21:44

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

–基思·汤普森(Keith Thompson)
2013年12月14日21:00

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

–基思·汤普森(Keith Thompson)
2013年12月14日在22:03

#3 楼

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

#!/usr/local/bin/python


将解释您的$PATH,并在$PATH的任何目录中找到python。可以在无需将python安装为/usr/bin/python/usr/local/bin/python或什至是自定义目录(已添加到$PATH)的系统(如/opt/local/bin/python)上安装的系统上进行修改。硬编码路径。

评论


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

–熊佳亚诺夫
13年11月22日,0:01

关于什么 #! python,为什么不使用它?

– kristianp
18年5月14日在23:54

#!不使用python,因为您必须将python二进制文件解释为该文件的完整路径,因此您必须将其与python二进制文件放在同一目录中。如果当前目录中没有python二进制文件,则会出现类似bash的错误:./script.py:python:错误的解释器:无此类文件或目录。就像您使用#!一样! / not / a / real / path / python

–蒂姆·肯尼迪
18年5月16日在16:14

@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”或die $!'时,我也遇到相同的故障。直到TIL,zsh所做的工作比必须要做的更多。

–乔纳森·托默
6月1日7:15



@TimKennedy aha,来自man zsh-misc:“如果由于文件不是可执行文件格式并且文件不是目录而导致执行失败,则假定它是Shell脚本。会生成/ bin / sh来执行它。如果程序是以'#!'开头的文件,则第一行的其余部分指定该程序的解释器。shell将在不处理内核中这种可执行格式的操作系统上执行指定的解释器。”因此,如果zsh在非POSIX系统上运行,则实现了自己的shebang-line解析,并且其实现不同于Linux内核。

–乔纳森·托默
6月1日7:30



#4 楼

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

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

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

除了/bin/sh之外,唯一一次在shebang中使用绝对路径的情况是脚本不是可移植的,因此您可以依靠口译员的已知位置。例如,仅在Linux上运行的bash脚本可以安全地使用#!/bin/bash。仅用于内部的脚本可以依赖内部解释器的位置约定。

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

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


#5 楼

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

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

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


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

评论


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

– AmokHuginnsson
15年5月25日在21:23

@AmokHuginnsson这是一个愚蠢的丑角。

– vonbrand
2月23日4:07

是的,但那是一种光怪陆离的效果。之所以存在,是因为无法解决较少的纠缠解决方案。

– DrHyde
2月24日21:59

如果sh不在/ bin中怎么办? Google Distroless容器的真实故事。

–LLlAMnYP
7月28日下午16:17

是的,如果perl不在路上,或者不在Amiga上,或者...如果您使用的是诸如此类的怪异方法,那么您就必须自己做一些工作。

– DrHyde
7月29日上午11:10

#6 楼

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

由于需要,因此需要使用/usr/bin/env(在其他操作系统中,/usr/bin并不如此)。只需在#!后面放置一个可执行文件名称-它必须是绝对路径。这是因为#!机制的工作水平低于外壳程序。它是内核二进制加载程序的一部分。可以测试。将此文件放入文件中并标记为可执行文件:

#!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进行运行。

#7 楼

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



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

env不能保证在/usr/bin/env中,而bash不能保证在/bin/bash/usr/bin/python中可以保证python。解释器(例如bash或python)。

这样可以防止您的脚本名称出现在env输出(或更改其显示方式/位置)中,并且无法使用例如,ps


[更新2016-06-04]

第三个问题:



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

ps -C scriptname.sh

将目录追加或预先添加到$ PATH很容易(尽管您仍然需要编辑文件以使其永久化-printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py或其他任何东西-而且编写脚本很难编辑,因为PATH可以在脚本中的任何地方设置,而不是在第一行中设置。而且比编辑~/.profile行要困难得多。

您仍然遇到使用#!会给您带来的所有其他问题。

@jlliagre在评论中建议#!/usr/bin/env很有用要使用多种解释程序版本测试脚本,“只需更改PATH / PATH顺序”即可。

如果需要这样做,在脚本顶部仅添加几行#!/usr/bin/env会更容易(它们只是第一行以外的任何地方的注释),然后将您要使用的内容剪切/复制并粘贴到第一行。

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




总体而言,使用nano的优势充其量是最小的,而且当然甚至无法抵消弊端。甚至“便利”优势在很大程度上是虚幻的。

IMO,这是一种特殊的程序员提倡的愚蠢的想法,他们认为操作系统不是要使用的东西,而这是一个需要解决的问题PS:这是一个简单的脚本,可一次更改多个文件的解释器。

#!/usr/bin/env

#!/bin/bash

interpreter=""
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.shchange-shebang.sh python2.7 *.py
的方式运行

评论


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

–cas
15-10-25在7:55

解释程序路径问题可以通过具有符号链接的sysadmin或用户编辑其脚本的方式轻松解决。鼓励人们忍受在此处以及其他问题上提到的在shebang上使用env引起的所有其他问题,这当然不是一个足够重大的问题。这以鼓励csh脚本促进不良解决方案的方式来推广不良解决方案:虽然可以,但是还有很多更好的选择。

–cas
2015年10月25日在8:37

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

–cas
15-10-25在10:51

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

– jlliagre
15-10-25在17:27

我希望我可以投票一千次,因为实际上这是一个很好的答案-如果有人使用/ usr / bin / env,并且我需要在本地删除它,或者如果他们不使用它,并且我需要添加它。两种情况都可能发生,因此此答案中提供的脚本是在工具箱中拥有的潜在有用工具。

– jstine
18年1月26日在14:57

#8 楼

在此处添加另一个示例:

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

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

env ruby --version

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

评论


//,好主意。多个解释器的全部目的不是破坏代码,也不是使代码依赖于特定的解释器。

– Nathan Basanese
16年1月15日在20:52

#9 楼

如果您纯粹是为自己或工作而写,而您要呼叫的口译员的位置始终在同一地方,则一定要使用直接路径。在所有其他情况下,请使用#!/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可执行文件都不应该信任用户提供的环境。

– Krazy Glew
16年6月3日在18:07

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

– MayeulC
18-10-7在11:28

#10 楼

出于可移植性和兼容性原因,最好使用

#!/usr/bin/env bash


而不是

#!/usr/bin/bash


有很多可能性,二进制文件可能位于Linux / Unix系统上。请查阅hier(7)联机帮助页以获取文件系统层次结构的详细说明。例如,FreeBSD将所有软件(不是基本系统的一部分)安装在/ usr / local /中。由于bash不属于基本系统,因此bash二进制文件安装在/ usr / local / bin / bash中。

当您需要可移植的bash / csh / perl /任何可运行的脚本时在大多数Linux发行版和FreeBSD中,您应该使用#!/ usr / bin / env。

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

评论


解释一下,“并非全世界都拥有Linux”。而且并非所有Linux系统都遵守hier(7)。大多数非Linux系统只是没有bash,或者它是一个隐藏在某处的“不受支持的非官方插件”。

– vonbrand
2月23日4:14