释伴:脚本解释器定义行
简介
计算机程序,按照其执行方式,可以分为两类:“编译型”和“解释型”。前者需要把程序翻译成为计算机能够读懂的二进制格式后才能执行,这个翻译工具通常称为编译器(Compiler),而后者则通过解释器对程序进行解释执行。我们通常使用的脚本,就属于后者,它们都需要对应的解释器进行支持。
脚本的运行,最基本的方法就是运行解释器,并把脚本文件作为其参数,以Perl为例:
|
|
然而这种方法比较繁琐,于是我们希望简化其用法,最好直接把脚本当作一个可执行的程序来使用,而不必每次都手工输入其对应的解释器。
这就需要在脚本中定义好应该使用什么解释器,通常这个定义就写到脚本的第一行,并以“#!”字符开头,这个脚本解释器定义行,英文称为“shebang”,中文翻译为“释伴”(兼具音译和意译):
|
|
进阶
从实现方式上深究,shell在执行脚本时,不过是把首行“释伴”中的命令提取出来,将原命令拼接到其后面,真正执行的是这个拼接后的命令,例如:
$ echo '#!/bin/echo hello' >> test.script
$ chmod +x test.script
$ ./test.script 123 xyz
这里构造了一个只有首行“释伴”的测试脚本,最后一行执行该脚本,其实就相当于执行了:
|
|
所以如下这个例子:
|
|
运行该“脚本”,相当于运行了“/bin/cat test2.script”命令,于是就把文件内容打印出来。
有时候,我们还可能看到一些特别的释伴写法,如:
#!/bin/bash --
在末尾多了两个“-”符号,通过上述研究,我们可以知道,这其实是让shell执行:
/bin/bash -- foo.sh [args...]
于是就能明白,这个“–”是为了禁用后续的参数解析,把后续“-”开头的参数都当作普通字符串解析,而不让它们干涉到bash自身的执行。
可移植性
写出尽可能适应不同运行环境的脚本,是一件值得追求的事。然而,在shebang中,要求给出解释器的全路径,否则,会出现如下报错:
|
|
这对于bash还好,因为基本上它都是位于/bin/bash
不变的(这也是我们见得最多的“#!/bin/bash”;当然也有安装bash到不同位置的场景)。但如果我们写的是其他语言的脚本,比如Perl或Python呢?这些解释器有可能位于不同的目录,比如“/usr/bin/
”或“/usr/local/bin/
”,甚至是其他用户自定义的目录。
这时候,可以通过/usr/bin/env
命令,来实现自动在PATH
环境变量中搜索到可用目录,以执行对应的解释器:
#!/usr/bin/env perl
或
#!/usr/bin/env python
扩展
来个扩展问题,能不能通过配置shebang,把一个编译型源码程序(如C/C++)变得像脚本一样呢?
|
|
然而,这种方式失败了,报错如下:
gcc: error: $0 && ./a.out && rm -f a.out: No such file or directory
它把整个“$0 && ./a.out && rm -f a.out
”字符串当作输入文件名扔给gcc了。
看来需要变通一下,让C源码被当作bash脚本来执行,这样,在bash中就可以更灵活地控制变量,生成临时文件,调度编译结果的执行等行为:
|
|
这次终于执行成功,该C程序表现得如同脚本一样了。也就是说,想要让某个C程序变成脚本执行,只需要在其开头增加两行(上面代码框中的第2、3两行)即可。