但在bash脚本中,我们写:
#!/bin/bash
在Python脚本中,有以下内容:
#!/bin/python
这是否意味着单独的
#
是一个注释,而 #!
不是?#!
行,然后在脚本运行时被忽略。你想知道shebang行和普通注释之间的区别是什么。
以#!
开头的行与以#
开头的任何其他行一样都是注释。无论#!
是文件的第一行还是其他位置,这都是正确的。 #!/bin/sh
有影响,但解释器本身不会读取它。
#
不是所有编程语言中的注释符号,但是你知道,在 Bourne 风格的 shell 中,它是一个注释符号,包括 sh
和 bash
(以及大多数非 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
会)。#!
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 可能存在于各种不同的位置。#!/usr/bin/env python
#!/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行。如果您这样做,并且该文件被标记为可执行文件,那么您可以像运行程序一样运行它......导致它被打开为文档。
一些应用程序有意使用此行为。例如,在VMware中,.vmx
文件定义虚拟机。您可以"运行"虚拟机,就好像它是一个脚本,因为这些文件被标记为可执行文件,并有Shebang行使其在VMware实用程序中打开。
rm
删除文件。它不是一种脚本语言。然而,一个以#!/bin/rm
开头并标记为可执行的文件可以被运行,当你运行它时,会调用rm
来删除它。
这通常被概念化为"文件删除自身"。但实际上,文件根本没有在运行。这更像是上面描述的.vmx
文件的情况。
尽管如此,因为#!
行方便了简单命令(包括命令行参数)的运行,你可以通过这种方式执行一些脚本。作为一个比#!/bin/rm
更复杂的"脚本"的简单示例,考虑以下内容:
#!/usr/bin/env tee -a
同时支持多种语言的脚本/程序,例如在没有该功能的操作系统中模拟哈希碰撞功能。
(这些程序被称为多语言,但不要与软件开发中的另一种意义的多语言混淆,即不同部分用不同语言编写的程序/项目。)
QBasic/QuickBASIC中的元命令,用于向编译器传递(对于已编译代码)代码生成选项,但它们是注释的一部分,因此在实际编译/解释过程中被忽略。
-x
标志有什么作用? - gerrit-x
"跳过第一行..." 第二行的编号变为 1
而不是 2
,第三行变为 2
而不是 3
,以此类推。这就是为什么你不应该使用那个标志。;) -x
用于在非类Unix操作系统上进行脚本编写,这些操作系统具有类似shebang语法但不以 #
开头(因此不是Python注释)的特点。 - Eliah Kaganperl script.pl
vs. ./script.pl
),那么解释器 会 读取shebang行来解析诸如 -w
的标志。尽管如此,不建议依赖这个特性。 - OrangeDog#!/path/to/awk -f
,文件的其余部分包含awk命令(没有-f,awk通常会难以解释剩下的内容)。 - Olivier Dulacawk
需要使用 -f
从文件中读取其“程序”。从某种意义上说,解释器是 awk -f
。手动调用 awk
运行 awk
脚本也需要使用 -f
:awk -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/python
#!
在脚本的初始行的前两个字符出现时被称为shebang
。它用于脚本中指示解释器进行执行。这个shebang
是针对操作系统(内核)而不是shell的,所以它不会被解释为注释。
参考资料:http://en.wikipedia.org/wiki/Shebang_%28Unix%29
一般来说,如果一个文件是可执行的,但实际上不是一个可执行(二进制)程序,并且存在这样一行,那么指定在#后面的程序将以脚本名和所有参数启动。这两个字符#和!必须是文件中的前两个字节!
详细信息:http://wiki.bash-hackers.org/scripting/basics#the_shebang
exec
系统调用使用,并且被解释器视为注释。./something
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
#!
进行比较。/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: /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透明地运行不同架构的可执行文件。
#include
。在这里,#
并不是用作注释。 - user25656