Please enable Javascript to view the contents

脚本第一行的 #! 是什么

 ·  ☕ 5 分钟  ·  🐞 neochin · 👀... 阅读

初识 #!

我们经常在一些脚本的第一行看到#!开头的一段注释,例如在shell脚本中:

1
2
#!/usr/bin/bash
echo hello world!

python脚本中:

1
2
#!/usr/bin/python
print("hello world!")

在我们熟知的几种脚本语言中,#一般被用作单行注释。但在类Unix环境下,第一行的#!还有另外一层意义:它告诉操作系统(程序加载器,即 program loader),在运行脚本时,使用哪一个解析指令(interpreter directive)来处理脚本。这一行特殊注释,称为shebang,也叫做sh-bangsha-banghashbangpound-banghash-bang
在上面的示例中,shebang指明分别使用/usr/bin/bash/usr/bin/python来分别运行shellpython脚本,最终打印hello world!

shebang

定义

shebang的格式:

1
#!interpreter [optional-arg]

其中:

  • shebang必须出现在脚本第一行,且顶格。
  • interpreter是一个可执行程序,并用绝对路径或者基于当前工作目录的相对路径写明。这里可执行程序的定义在不同操作系统上定义不同。在Linux中,可执行程序是指具有可执行权限的、可以直接运行的程序,可以是可执行二进制程序,也可以是脚本。在Solaris-Darwin-的衍生操作系统中(比如MacOS),可执行程序是指可执行的二进制程序,而不能是脚本。
  • #!interpreter之间可以有空格,但通常不使用空格。
  • 可以传递多个参数。

调用示例

/tmp/目录下新建shebang目录,目录结构:

1
2
3
shebang
├── myscript_a
└── myscript_b

myscript_a

1
2
3
#!/usr/bin/bash
echo $1
cat $1

myscript_b

1
2
#!/tmp/shebang/myscript_a
echo hello world

myscript_a使用bash作为解析指令,输出第一个参数,并且显示对应文件内容。
myscript_b使用myscript_a作为解析指令,最终会当作文本显示全部内容。
先添加脚本的可执行权限,然后执行:

[leo@leo-m shebang]$ chmod +x myscript_*
[leo@leo-m shebang]$ ./myscript_b 
./myscript_b
#!/tmp/shebang/myscript_a
echo hello world

我们可以看到,脚本的运行效果如同脚本的直接调用:

[leo@leo-m shebang]$ ./myscript_a ./myscript_b 
./myscript_b
#!/tmp/shebang/myscript_a
echo hello world

使用env的shebang

使用env的目的

env作为一个命令,经常被用来列举当前用户的所有环境变量。而我们也常在shebang中使用它。

python脚本举例,这种写法也经常出现:

1
#!/usr/bin/env python

虽然最终都是使用python来解析脚本,那么它跟#!/usr/bin/python有何区别呢?
其实,使用env的方式,只是比直接指定路径的方式,多了一些灵活性。env会在环境变量PATH中查找python所在路径,最终使用找到的第一个python来解析脚本。我们可以使用echo $PATH在终端查看自己的PATH目录。

使用env的方式,最大的优势就是实现不同环境上的兼容性。因为在不同的系统中,python等可执行程序可能存在的目录不一样。举个例子,python可能放在/usr/bin下,或者/opt/python$HOME/python等目录(当然这些目录需要添加到PATH环境变量中,env才能找到),如果直接指定指定/usr/bin/python导致找不到python,最终无法正常执行。

使用env也有一个小风险,就是恶意人士可能在PATH目录下放置一个同名的假程序,脚本执行时就会运行这个恶意程序。当然这个操作需要一定的权限(比如root、或者其它具有sudo等授权),当他都拥有了这种权限之后,可能也不屑于做这样的事了。

env 的shebang定义

1
2
#!/usr/bin/env command
#!/usr/bin/env -[v]S[option]… [name=value]… command [args]…

我们看个容易出错误的地方,用perl作例子,文件名hello.pl

1
2
#!/usr/bin/env perl -T -w
print "hello\n";

脚本只是输出hello,但尝试运行时出错:

[leo@leo-m shebang]$ chmod +x hello.pl
[leo@leo-m shebang]$ ./hello.pl 
/usr/bin/env: “perl -T -w”: 没有那个文件或目录
/usr/bin/env: 使用 -[v]S 以在 shebang 行中传递选项

错误信息中可以看出,env的把perl -T -w整个一串当成程序名,肯定找不到了。

那么使用env如何传参数呢?那就是使用-S选项,看hello_u.pl

1
2
#!/usr/bin/env -S USER=neochin perl -T -w
print "hello " . $ENV{'USER'} . "\n";

脚本需要环境变量USER(单纯想展示一下使用env写法还可以临时设置环境变量的优势功能),我们通过env的参数指定为neochin-Ssplit分割的意思,就是指明后面的字符串要拆开来解析。运行效果:

[leo@leo-m shebang]$ chmod +x hello_u.pl
[leo@leo-m shebang]$ ./hello_u.pl 
hello neochin

灵活的注释

在日常开发过程中,并非所有的注释都是只是给程序员看的。
比如在python2时代,我们通常使用单独一行注释# -*- coding: utf-8 -*-,指定python脚本的默认编码为utf-8

[leo@leo-m tmp]$ cat p.py 
print("中文")
[leo@leo-m tmp]$ python2 p.py
  File "p.py", line 1
SyntaxError: Non-ASCII character '\xe4' in file p.py on line 1,
but no encoding declared;
see http://python.org/dev/peps/pep-0263/ for details
[leo@leo-m tmp]$ 
[leo@leo-m tmp]$ 
[leo@leo-m tmp]$ cat p1.py
# -*- coding: utf-8 -*-
print("中文")
[leo@leo-m tmp]$ python2 p1.py
中文

又比如在团队协作时,通常需要输出API文档。为了减少单独编写及维护成本,我门通常使用第三方文档工具swaggerdoxygen等,通过扫描代码、注释来完成文档的自动化生成。

同时,注释还很好玩。用一个源码文件,支持不同语言运行,请看Polyglot。起飞吧,少年,秀儿就是你!

参考资料

打不开链接的,请科学上网。

您的鼓励是我最大的动力
alipay QR Code
wechat QR Code

neochin
作者
neochin
BUG Developer