使用 #!/usr/bin/env 和 #!/bin/env 在 shebang 中有什么区别?

17

有区别吗,还是只是个人选择?

4个回答

21

#!<interpreter> <arguments> 尝试运行 <interpreter> <arguments> 以读取和运行文件的其余部分。

因此,#!/usr/bin/env 表示必须有一个名为 /usr/bin/env 的程序;
#!/bin/env 表示必须有一个名为 /bin/env 的程序。

一些系统具有其中一个而不具有另一个。

根据我的经验,大多数系统都有 /usr/bin/env,因此 #!/usr/bin/env 更常见。

Unix 系统将尝试使用 execve 运行 <interpreter>,这就是为什么它必须是完全路径,并且没有路径的 #!env 无法工作的原因。


1
此外,非OSX BSD系统没有/bin/bash,因此建议在那里使用/usr/bin/env以实现可移植性。另外,如果您想在$PATH中的不同目录中运行较新版本的BASH,则env会尊重并使用该目录,而/bin/bash显然是硬编码的。 - kvz
4
特别地,OS/X有 /usr/bin/env,但没有从 /bin/env/usr/bin/env 的符号链接。如果你遇到 -bash: ./your_command: /bin/env: bad interpreter: No such file or directory 错误,那么就会出现问题。 - Charles Merriam

7

历史上,UNIX有两批二进制文件:

  • / 根文件系统在启动时就挂载。
  • /usr 可能会在之后挂载,可能运行来自 / 的脚本和程序以安排挂载。例如:一些站点通过从网络挂载 /usr 来节省空间,但需要先连接到网络。例如:它是一个大型的本地文件系统,但如果它损坏了,你需要像 fsck 这样的工具来尝试修复它。

因此,/bin/sbin(使用 /lib)必须包含至少一个位于 /bin/sh 的 shell、如 /bin/echo/bin/test 等基本脚本,以及诸如 /bin/mount/sbin/mount/sbin/fsck 等系统工具......

因此,在不同的 Unix 中,几乎任何程序都可能会:

  • 在 /usr/bin/ 中但不在 /bin/ 中
  • 在 /bin/ 中但不在 /usr/bin/ 中
  • 同时在两个位置中作为符号链接
  • 同时存在于两个位置,但是不同!例如,某些系统使用非常简化的 shell(例如 dash)作为 /bin/sh 来加快启动速度,但是建立符号链接将 /usr/bin/sh -> /usr/bin/bash(如果我没记错,用 "sh" 调用 bash 会将其置于某种 posix 模式下,但它仍然是不同的更强大的 shell)。

大多数情况下,只需设置 $PATH 包括两个位置。但是一些上下文环境,如 #!docker exec 需要一个固定的完整路径。因此,使用 env 编写可移植脚本的技巧 - env 恰好执行 PATH 查找。

有何变化

这些都是有效的用例,但现代 Linux 对 / 本身也遵循了类似的“需要小用户空间来挂载 / 恢复主用户空间”的论点!引入了 pivot 系统调用和 initrd,并且工具已经成长到可以将你需要的部分复制进去。

/usr 统一

现在,//usr 可以说已经失去了意义。在一个文件系统上同时拥有它们并建立符号链接在原则上对每个人都可行,尽管一些特定的设置会出问题并需要更改......

参见2012年的文章https://lwn.net/Articles/483921/,了解有关“/usr合并”想法的概述。例如Fedora已完成此操作:https://fedoraproject.org/wiki/Features/UsrMove。许多其他发行版还没有这样做,或者仍在讨论中,或正在解决一些问题以减少用户出现问题的概率。例如,请参见Debian的准备工作:https://wiki.debian.org/UsrMerge.

/usr/local/、~/.local/和其他位置

希望使用PATH查找的原因有更多:

  • 并非所有Unix都默认安装所有语言,或者他们打包的是太旧的版本。因此,虽然像env这样的东西总是存在于/bin或/usr/bin中,或者两者都有,但一个人可能只有(或更喜欢)在/usr/local或家目录下安装python和其他解释器...
  • 许多语言版本/库管理器有一种运行隔离环境的方法,并且经常使用可执行的“shims”来激活这些环境。例如,Python的“虚拟环境”目录有一个bin/python3二进制文件;因此,当您将该bin/目录添加到PATH中,并且如果运行使用#!/usr/bin/env python3的脚本,则这就是您需要使用特定于该环境的模块所需的全部内容。

回到问题本身,对于env本身要使用什么完整路径?

按照同样的逻辑,在具有不同/usr的系统中,env本身可能会缺失其中之一,因此您无法编写100.00%的可移植的#!命令行。
在实践中,两者都很可能有效。我没有统计数据,但实际上多年来我看到的更常见的形式是/usr/bin/env(例如示例)。


2

Mikel的解释很好,只是漏掉了一个很重要的小事情,那就是只传递了一个参数,包括所有空格:

#!<Interpreter> <argument>

调用结果:

$ <Interpreter> '<argument>' path_to_calling_script

例如,假设:
$ cat /tmp/test
#!/usr/bin/env python
print "hi"

$ /tmp/test

这句话的意思是和下面代码的效果相同:

$ /usr/bin/env "python" /tmp/test

引号的作用是显示如果您添加任何标志或其他值,它们将成为被调用的参数的一部分。
 #!/bin/bash -c /bin/env python

将被解释为:

 $ /bin/bash "-c /bin/env python"

这是行不通的。


1

/usr/bin/env 是指向 /bin/env 的软链接。实际上,你正在使用的是 /bin/env


哪个系统同时具有硬文件? - kurumi
7
Ubuntu 没有 /bin/env。而 Solaris 8 分别拥有这两个文件。 - glenn jackman
4
大多数系统根本没有/bin/env,而是/usr/bin/env在标准位置。 - Good Person
Centos 4有/bin/env,而/usr/bin/env是一个符号链接。 - Jared Beck
https://www.in-ulm.de/~mascheck/various/shebang/#env 还提到了相反的情况:“另一方面,至少在SCO OpenServer 5.0.6和Cray Unicos 9.0.2上只有/bin/env(尽管后者仅具有历史意义)。 ”该页面对#!可移植性的所有方面进行了广泛的研究。 - Beni Cherniavsky-Paskin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接