Shebang Shenanigans
You have probably all seen the shebang at top of shell scripts, the first line starting with #!/bin/sh
.
你可能看到过一些以#!/bin/sh
开头的脚本
The initial characters #!
tells the OS that this isn’t a regular binary, but rather something that needs to run through an interpreter, namely the interpreter after #!
. Therefore you can see lines like #!/usr/bin/perl
, #!/usr/bin/awk
, or #!/usr/bin/python
.
#!
告诉操作系统,这个文件不是一个常规的二进制文件,是需要#!
后面标注的解释器来执行的,所以你可以看到有这些写法#!/usr/bin/perl
,#!/usr/bin/awk
,#!/usr/bin/python
Executing a file like ./test.sh
, having the shebang #!/bin/sh
, is similar to calling this command: /bin/sh ./test.sh
.
在命令行中执行首行标注着#!/bin/sh
的脚本./test.sh
与直接执行/bin/sh ./test.sh
是类似的。
While learning awk, I noticed the use of the shebang #!/usr/bin/awk -f
, i.e. a shebang with extra arguments. This can also sometimes be seen for Python, usually as #!/usr/bin/python -u
to enable unbuffered output.
在学习awk命令的时候,我们标注的shebang为#!/usr/bin/awk -f
,也就是,一个具有额外参数的shebang,这种模式同样也出现在python中,比如#!/usr/bin/python -u
去保证脚本的无缓冲输出。
实际用处?当程序输出(包括错误输出)保存到文件时,为防止乱行,一般加上此参数。
I wanted to add some extra arguments to (g)awk, ideally running it as #!/usr/bin/awk -i inplace -f
, to modify the file in-place. Surprisingly (to me), this didn’t work. It turns out that this is equivalent to the command: awk "-i inplace -f" file.awk
, i.e calling awk
with a single argument with all flags mashed into a single string. Not what I intended, and certainly not something that worked. This led me to the question: How does different Unix-like systems handle shebang arguments?.
我想额外添加参数到awk命令上,像这样#!/usr/bin/awk -i inplace -f
,使其直接对文件做inplace操作。但是另我感到吃惊的是,没用。这个shebang等价于命令行中的awk "-i inplace -f" file.awk
,也就是将所有的falgs混合成一个字串组成一个参数来调用awk命令。是我始料未及的,所以我就在想,不同的unix类os如何处理shebang 的参数的。
Let’s investigate!
Helpers
To easily see how arguments are passed to a binary, a wrote a small helper application in C. It prints one line for every argument passed.
为了看到参数是如何传入到二进制命令里面的,我写个c的应用。,其打印每一个输入的参数。、
#include <stdio.h>
int main(int argc, char **argv)
{
for (int i = 0; i < argc; ++i) {
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
Running it like ./args hi there reader
provides the following output:
在命令行中输入./args hi there reader
,有如下输出
gcc args.c -std=c99
./a.out hi there reader
# 编译后生成的可执行文件是 a.out
$ ./args hi there reader
argv[0]: ./args
argv[1]: hi
argv[2]: there
argv[3]: reader
I copy this binary to /usr/local/bin/args
, and then proceed to create the following test file, and make it executable with chmod +x file.txt
.
将二进制文件复制到/usr/local/bin/args
,并创建测试文件,可执行
#!/usr/local/bin/args -a -b --something
# file.txt
hello i'm a line that doesn't matter
Linux
执行
[root@linux ~]# ./file.txt
argv[0]: /usr/local/bin/args
argv[1]: -a -b --something
argv[2]: ./file.txt
As we can see, argv[1]
has all flags stored as a single argument. 😦
macOS
This worked exactly as I expected initially! Here each argument is passed independently to the interpreter.
$ ./file.txt
argv[0]: /Users/linus/args
argv[1]: -a
argv[2]: -b
argv[3]: --something
argv[4]: ./file.txt
FreeBSD / OpenBSD
Nothing exciting here, it turns out both these systems work the same way as Linux regarding shebangs.
$ ./file.txt
argv[0]: /usr/local/bin/args
argv[1]: -a -b --something
argv[2]: ./file.txt
OpenIndiana
I also wanted to try out a Solaris-fork, in this case OpenIndiana, to try out different Unixes. Turns out this provided different results as well:
$ ./file.txt
argv[0]: /usr/local/bin/args
argv[1]: -a
argv[2]: ./file.txt
Copy
As we can see, OpenIndiana completely throws away anything except the first argument. (What? ಠ_ಠ )
Summary⌗
The results can be summarized in the following table:
argv[0] | argv[1] | argv[2] | argv[3] | argv[4] | |
---|---|---|---|---|---|
Linux | /usr/local/bin/args | -a -b --something | ./file.txt | ||
FreeBSD OpenBSD | /usr/local/bin/args | -a -b --something | ./file.txt | ||
macOS | /usr/local/bin/args | -a | -b | --something | ./file.txt |
OpenIndiana | /usr/local/bin/args | -a | ./file.txt |
— 总结
— 博主总结
shebang的参数最好写一块