#!/bin/sh 这个命令是由解释器读取的吗?

在bash或sh中,我猜以#开头的任何内容都是注释。
但在bash脚本中,我们写:
#!/bin/bash

在Python脚本中,有以下内容:
#!/bin/python

这是否意味着单独的 # 是一个注释,而 #! 不是?

1一旦你开始查看Apparmor配置文件,你会看到#include。在这里,#并不是用作注释。 - user25656
4@vasa1但是关于Shell脚本开头的井号行的一个经常被忽视的关键点是,它们实际上是注释。 - Eliah Kagan
4个回答

在脚本运行之前,使用#!行,然后在脚本运行时被忽略。

你想知道shebang行和普通注释之间的区别是什么。

#!开头的行与以#开头的任何其他行一样都是注释。无论#!是文件的第一行还是其他位置,这都是正确的。 #!/bin/sh 有影响,但解释器本身不会读取它

# 不是所有编程语言中的注释符号,但是你知道,在 Bourne 风格的 shell 中,它是一个注释符号,包括 shbash(以及大多数非 Bourne 风格的 shell,比如 csh)。在 Python 中,它也是一个注释符号。而且,在一些不是真正脚本的配置文件中(比如 /etc/fstab),它也是一个注释符号。

假设一个 shell 脚本以 #!/bin/sh 开头。这是一个注释,解释器(即 shell)会忽略 # 字符后面的所有内容。

#! 行的目的不是向解释器提供信息。#! 行的目的是告诉操作系统(或者启动解释器的任何进程)要使用什么作为解释器

如果您将脚本作为可执行文件调用,例如通过运行./script.sh,系统会查看第一行是否以#!开头,后面跟着零个或多个空格,再跟着一个命令。如果是这样,它将使用脚本的名称作为参数来运行该命令。在这个例子中,它会运行/bin/sh script.sh(或者严格来说,是/bin/sh ./script.sh)。
如果您通过显式调用解释器来调用脚本,则不会查看#!行。因此,如果您运行sh script.sh,第一行没有任何效果。如果script2.sh的第一行是#!/usr/games/nibbles,运行sh script2.sh将不会尝试在nibbles中打开脚本(但./script2.sh会)。
你会注意到,在这两种情况下,脚本的扩展名(如果有的话)并不影响它的运行。在类Unix系统中,这通常不会影响脚本的运行方式。在其他一些系统上,比如Windows,系统可能会完全忽略#! shebang行,并且扩展名可能决定了脚本的运行方式。(这并不意味着你需要给你的脚本添加扩展名,但如果你这样做了,它们应该是正确的原因之一。)
选择#!来完成这个目的,正是因为#开始一个注释。#!行是给系统看的,而不是解释器,解释器应该忽略它。
Bash脚本的Shebang行
你(最初)说你在bash脚本中使用#!/bin/sh。只有在脚本不需要任何bash扩展的情况下才应该这样做--sh需要能够运行脚本。sh并不总是指向bash的符号链接。通常,在所有最新的Debian和Ubuntu系统中,包括here,sh是指向dash的符号链接。
Python脚本的Shebang行
你还说(在问题的第一个版本中,在编辑之前)你用#!/bin/sh read by the interpretor开头你的Python脚本。如果你是字面意思,那么你绝对应该停止这样做。如果hello.py以那一行开始,运行./hello.py将会执行:
/bin/sh read by the interpretor hello.py

/bin/sh 会尝试执行一个名为 read 的脚本(带有 by the interpretor hello.py 作为参数),希望能找不到 read,这样你的 Python 脚本就永远不会被 Python 解释器看到。

如果你犯了这个错误但没有遇到我描述的问题,那么你可能是通过显式指定解释器来调用你的 Python 脚本(例如,python hello.py),导致第一行被忽略。当你将脚本分发给他人或者很久以后再使用时,可能不清楚这是必要的。最好现在就修复它们。或者至少完全删除第一行,这样当它们无法通过 ./ 运行时,错误消息会有意义。

对于 Python 脚本,如果你知道 Python 解释器的位置(或即将出现的位置),你可以以相同的方式编写 #! 行:

#!/usr/bin/python

或者,如果它是一个 Python 3 脚本,你应该指定 python3,因为 python 几乎总是 Python 2

#!/usr/bin/python3

然而,问题在于虽然 /bin/sh 应该始终存在,而且在带有操作系统的系统中几乎总是存在 /bin/bash,但 Python 可能存在于各种不同的位置。
因此,许多 Python 程序员使用以下方法代替:
#!/usr/bin/env python

(或者对于Python 3,使用#!/usr/bin/env python3。)
这样脚本就依赖于env在“正确的位置”,而不是依赖于python在正确的位置。这是一件好事,因为:
  • env几乎总是位于/usr/bin
  • 在大多数系统上,应该运行您的脚本的python是出现在PATH中的第一个。以#!/usr/bin/env python开头的hello.py使得./hello.py运行/usr/bin/env python hello.py,这(几乎)等同于运行python hello.py
不能使用#!python的原因是:
  • 你想要指定解释器的绝对路径(即以 / 开头)。
  • 当命令不包含斜杠时,在当前目录下调用的进程将执行 python 在当前目录中。这是特定的shell行为,会在路径中搜索。

偶尔,一个不是shell脚本的Python或其他脚本可能会有一个以 #!/bin/sh ... 开头的shebang行,其中的 ... 是一些其他代码。这有时是正确的,因为有一些方法可以使用参数调用Bourne兼容的shell(sh),从而使其调用Python解释器。(其中一个参数可能会包含 python)。然而,对于大多数情况,#!/usr/bin/env python 更简单、更优雅,并且更有可能按照你期望的方式工作。

其他语言中的Shebang行

许多编程和脚本语言,以及一些其他文件格式,使用 # 作为注释符号。对于它们中的任何一种,可以通过在 #! 后面的第一行指定程序来运行该语言的文件,程序将以该文件作为参数。

在某些编程语言中,#通常不是注释,但特殊情况下,如果第一行以#!开头,则会忽略该行。这样即使#不会将行变为注释,也可以使用#!语法。

对于不作为脚本运行的文件的Shebang行

虽然不太直观,但任何文件格式允许以#!开头,后跟可执行文件的完整路径的文件都可以有Shebang行。如果您这样做,并且该文件被标记为可执行文件,那么您可以像运行程序一样运行它......导致它被打开为文档。

一些应用程序有意使用此行为。例如,在VMware中,.vmx文件定义虚拟机。您可以"运行"虚拟机,就好像它是一个脚本,因为这些文件被标记为可执行文件,并有Shebang行使其在VMware实用程序中打开。

对于不作为脚本运行但仍然像脚本一样运行的文件的Shebang行

rm删除文件。它不是一种脚本语言。然而,一个以#!/bin/rm开头并标记为可执行的文件可以被运行,当你运行它时,会调用rm来删除它。

这通常被概念化为"文件删除自身"。但实际上,文件根本没有在运行。这更像是上面描述的.vmx文件的情况。

尽管如此,因为#!行方便了简单命令(包括命令行参数)的运行,你可以通过这种方式执行一些脚本。作为一个比#!/bin/rm更复杂的"脚本"的简单示例,考虑以下内容:

#!/usr/bin/env tee -a

这个程序可以与用户进行交互式输入,逐行将输入内容回显给用户,并将其追加到“脚本”文件的末尾。
有用吗?不是很有用。概念上有趣吗?完全有!是的。(有点儿)

概念上类似的编程/脚本概念(只是为了好玩)


@Rinzwind 谢谢!(顺便说一句,这个答案并非来自其他地方,如果你有这样的疑问。) - Eliah Kagan
@Rinzwind 别担心,1小时内获得8个赞很可能会进一步增加 :-) - guntbert
1如果它总是被忽略,那么Python的-x标志有什么作用? - gerrit
4@gerrit 好问题。在任何一个编译器报告带有行号的消息的语言中,注释的内容会被忽略,但是注释行仍然会被计算。在代码行之前添加注释或空行仍然会导致该行代码的行号增加。-x "跳过第一行..." 第二行的编号变为 1 而不是 2,第三行变为 2 而不是 3,以此类推。这就是为什么你不应该使用那个标志。;) -x 用于在非类Unix操作系统上进行脚本编写,这些操作系统具有类似shebang语法但不以 # 开头(因此不是Python注释)的特点。 - Eliah Kagan
4在Perl中,如果解释器直接启动(perl script.pl vs. ./script.pl),那么解释器 读取shebang行来解析诸如 -w 的标志。尽管如此,不建议依赖这个特性。 - OrangeDog
一个需要至少在shebang行上有一个参数才能使shebang工作的程序示例:awk:通常第一行是#!/path/to/awk -f,文件的其余部分包含awk命令(没有-f,awk通常会难以解释剩下的内容)。 - Olivier Dulac
@OlivierDulac 是的 - awk 需要使用 -f 从文件中读取其“程序”。从某种意义上说,解释器是 awk -f。手动调用 awk 运行 awk 脚本也需要使用 -fawk -f script。这并不使 awk 特别特殊。例如,一个希望使用无缓冲 I/O 运行的 Python 脚本可以以 #!/usr/bin/env python -u 开头,并且可以通过 ./program(如果在 PATH 中)或 python -u program 运行。相比之下,当 perl 像 OrangeDog 描述的那样行为时,情况就大不相同了 - 即使没有指定标志,解释器也会从 #! 行获取标志。 - Eliah Kagan

一个 shebang 是由字符数字符号和感叹号组成的字符序列(例如 "#!"),当它出现在脚本的初始行的前两个字符时。

在 *nix 操作系统中,当以 shebang 开头的脚本运行时,程序加载器将解析脚本初始行的其余部分作为解释器指令;指定的解释器程序将被运行,并将最初尝试运行脚本时使用的路径作为参数传递给它。例如,如果脚本的路径名为 "path/to/your-script",并且它以以下行开头:

#!/bin/sh

然后,程序加载器被指示运行程序"/bin/sh",例如Bourne shell或兼容的shell,并将"path/to/your-script"作为第一个参数传递。
因此,如果脚本以路径"path/to/python-script"命名,并以以下行开始:
#!/bin/python

然后加载的程序被指示运行程序"/bin/python",例如Python解释器,并将"path/to/python-script"作为第一个参数传递。
简而言之,"#"将注释掉一行,而字符序列"#!"出现在脚本的初始行的前两个字符位置上具有上述意义。
详细信息请参见为什么有些脚本以#!开头...? 来源:本答案的某些部分(稍作修改)源自Shebang (Unix)英文维基百科上(由维基百科贡献者)。本文采用与AU上的用户内容相同的CC-BY-SA 3.0许可证,因此可以在署名的情况下进行派生。

#!在脚本的初始行的前两个字符出现时被称为shebang。它用于脚本中指示解释器进行执行。这个shebang是针对操作系统(内核)而不是shell的,所以它不会被解释为注释。

参考资料:http://en.wikipedia.org/wiki/Shebang_%28Unix%29

一般来说,如果一个文件是可执行的,但实际上不是一个可执行(二进制)程序,并且存在这样一行,那么指定在#后面的程序将以脚本名和所有参数启动。这两个字符#和!必须是文件中的前两个字节!

详细信息:http://wiki.bash-hackers.org/scripting/basics#the_shebang


不,它只被Linux内核的exec系统调用使用,并且被解释器视为注释。
当你在bash上执行时:
./something

在Linux上,这将使用路径./something调用exec系统调用。
内核中的这行代码在传递给exec的文件上被调用:https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

它读取文件的前几个字节,并将其与#!进行比较。
如果比较结果为真,则Linux内核会解析该行的剩余部分,然后使用路径/usr/bin/env python和当前文件作为第一个参数进行另一个exec调用。
/usr/bin/env python /path/to/script.py

并且这适用于使用#作为注释字符的任何脚本语言。

是的,你可以用以下代码创建一个无限循环:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

Bash 能识别这个错误。
-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#!只是恰好可读,但这并非必需。

如果文件以不同的字节开头,那么exec系统调用将使用不同的处理程序。另一个最重要的内置处理程序是用于ELF可执行文件的:https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305,它检查字节7f 45 4c 46(这也恰好可读为.ELF)。让我们通过读取/bin/ls的前4个字节来确认这一点,它是一个ELF可执行文件:

head -c 4 "$(which ls)" | hd 

输出:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

所以当内核看到这些字节时,它会将ELF文件正确地放入内存,并使用它启动一个新的进程。参见:https://stackoverflow.com/questions/8352535/how-does-kernel-get-an-executable-binary-file-running-under-linux/31394861#31394861

最后,你可以使用binfmt_misc机制添加自己的shebang处理程序。例如,你可以添加一个自定义处理.jar文件的处理程序。该机制甚至支持按文件扩展名进行处理。另一个应用是使用QEMU透明地运行不同架构的可执行文件

我不认为POSIX规范指定了shebangs,然而:https://unix.stackexchange.com/a/346214/32558,尽管它在解释部分提到了,并以“如果系统支持可执行脚本,则可能会发生某些情况”的形式提及。