2010-06-09 51 views
37

在脚本中,您必须在第一行包含一个#!,后面跟着将执行脚本的程序的路径(例如:sh,perl)。#怎么样! shebang的工作?

据我所知,#字符表示注释的开始,该行应该被执行脚本的程序忽略。看起来,第一行在某些时候被某些东西读取,以便脚本被正确的程序执行。

请问有人可以更多地了解#!的工作原理吗?

我对此很好奇,所以答案越深入越好。

+0

我对这个主题的comp.lang.shell线程[可执行程序后记(http://groups.google一个良好的教育。 com/group/comp.unix.shell/browse_thread/thread/e7a3306342c01847/ec5741ed3278408a?q = executable + postscript + programs#ec5741ed3278408a)通过编写一个简单的C程序来操作命令行,我能够为通常不会这样做的语言。 – 2011-10-06 22:36:31

回答

30

推荐阅读:

Unix内核的程序加载器是负责做这个。当调用exec()时,它会要求内核在其参数中从文件加载程序。然后它会检查文件的前16位,以查看它具有的可执行格式。如果它发现这些位是#!它将使用该文件的第一行的其余部分来查找它应该启动哪个程序,并且它提供了它尝试启动的文件的名称(脚本)作为最后一个参数口译员计划。

解释器然后运行正常,并将#!作为注释行。

+0

最好的事情是该文件可以是任何东西,不一定是程序 - 只要被调用的程序可以容忍shebang。 – 2014-07-15 13:14:18

+0

@KevinPanko:内核的程序加载器是否总是正好16位?如果'#!'之前是UTF-8或UTF-16 BOM,那么会发生什么? – stakx 2017-01-22 17:17:04

+1

@stakx是的,这是在Unicode之前发明的,从那以后一直没有改变过。 http://unicode.org/faq/utf_bom.html#bom5 – 2017-01-23 21:07:17

8

简单地说:井号(#!)线由 读取壳(例如 shbash等) 操作系统的程序加载器。虽然它在形式上看起来像一条评论,但它是文件的前两个字节,将整个文件标记为文本文件和脚本。该脚本将被传递到shebang之后的第一行中提到的可执行文件。瞧!


略长的故事:想象一下,你有你的脚本,foo.sh,与可执行位(x)集。这个文件包含例如以下内容:

#!/bin/sh 

# some script commands follow...: 
# *snip* 

现在,你的shell,键入:

> ./foo.sh 

编辑:同时请参阅下列后或您阅读以下之前阅读评论!事实证明,我错了。显然不是将脚本传递给目标解释器的外壳,而是操作系统(内核)本身。

请记住,你的shell进程中输入这个(我们假设这是程序/bin/sh)。因此,该输入必须由该程序处理。它将该行解释为一个命令,因为它发现该行输入的第一个内容是实际存在的文件的名称,并且该文件的可执行位已设置。

/bin/sh然后开始读取文件的内容并在文件的最开始处发现shebang(#!)。对于shell而言,这是一个令牌(“幻数”),通过它可以知道该文件包含脚本。

现在,它如何知道脚本编写的是哪种编程语言呢?毕竟,你可以执行Bash脚本,Perl脚本,Python脚本,......到目前为止,所有的shell都知道它正在查看一个脚本文件(这不是一个二进制文件,而是一个文本文件)。因此它读取下一个输入直到第一个换行符(这将导致/bin/sh,与上面相比)。这是脚本将通过执行的解释器。 (在这种情况下,目标解释器是shell本身,因此它不必为脚本调用新的shell;它只处理脚本文件本身的其余部分)。

如果脚本是注定的例如Perl解释器会(可选地)必须做的所有事情就是查看shebang行是否真的提到了Perl解释器。否则,Perl解释器会知道它不能执行这个脚本。如果确实在shebang行中提到了Perl解释器,它会读取脚本文件的其余部分并执行它。

+4

可执行文件的前两个字节是指示应该如何执行的幻数;对于解释的脚本,前两个字节方便地对应于ASCII字符'#!' – friedo 2010-06-09 19:44:04

+7

它不是查看这两个字节的shell,而是系统(程序加载器),是吗?无论您是否在shell中运行脚本,都会发生同样的情况。 – Cascabel 2010-06-09 19:45:50

+4

shebang不是由shell处理的,它由操作系统本身处理。 – 2010-06-09 19:46:12

1

Linux内核exec系统调用使用初始字节#!来识别文件类型

当您在bash中做:

./something 

在Linux上,这要求有充分的exec系统调用路径something

这条线被调用在内核上传递到exec文件:https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

如果((bprm-> BUF [0] = '#')||(bprm-> BUF [1 ]!='!'))

它读取文件的第一个字节,并将它们与#!进行比较。

如果比较结果为真,那么该行的其余部分是由Linux内核,这使得与路径/usr/bin/env python和当前文件作为第一个参数另一个exec调用解析:

/usr/bin/env python /path/to/script.py 

,这适用于任何使用#作为注释字符的脚本语言。在路径

#!/a 

和可执行文件/a

#!是人类可读的,但是这是没有必要的:

是的,你可以无限循环使用。

如果文件以不同的字节开始,那么exec系统调用将使用不同的处理程序。另一个最重要的内置处理程序是用于ELF可执行文件:https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305,它检查字节7f 45 4c 46(它也碰巧是人类可读的.ELF),它读取elf文件,将其正确存入内存,并开始一个新的进程它。另请参见:How does kernel get an executable binary file running under linux?

此外,您还可以添加自己的家当处理程序的binfmt_misc机制。例如,您可以为.jar文件添加自定义处理程序:Running a JAR file without directly calling `java`此机制甚至通过文件扩展名支持处理程序。 http://stackoverflow.com/questions/3009192/how-does-the-shebang-work/40938907#40938907

我不认为POSIX指定但是shebangs:https://unix.stackexchange.com/a/346214/32558