Unix环境下的shell脚本通常都是#!/bin/sh开头,那么这句描述符究竟是什么含义呢,我试图解答这个问题。
#!出现的位置
首先#!必须出现在shell脚本的开头位置,后面跟一个shell解析器,比如/bin/sh,或者/bin/bash,或者/usr/bin/ksh, 等等.
看一个例子
$ cat a.sh
#!/bin/bash
readlink /proc/$$/exe
$ ./a.sh
/bin/bash
我们看到 a.sh使用的可执行程序是/bin/bash,这个/bin/bash是在a.sh文件里面通过#!指定的。
再看另一个例子a.out
$ cat a.c
#include <stdio.h>
int main()
{
printf("Hello a.out!\n");
return 0;
}
$ gcc t.c
$ ./a.out
Hello a.out!
$ bash ./a.out
./a.out: ./a.out: cannot execute binary file
a.out是一个可执行程序(ELF格式),它可以独自运行,但是不能通过一个shell来运行。
分析
#!实际上就是文件的魔数(magic number)
我们知道ELF格式为文件头4个字符是".ELF",即(0x 7f 45 4c 46),而其实字符"#!"是shell脚本文件的魔数,即(0x 23 21),因为shell脚本是文本文件,#!就是两个可读的魔数字符。
这个魔数是干什么用的呢?它是被操作系统exec系列函数使用的,exec函数需要加载一个文件时,它会读取文件开头魔数域,如果是".ELF",那么就是一个ELF格式的可执行文件,如果是"#!"那么就是一个脚本文件,然后再从"#!"后面继续读取脚本解析器,最后调用脚本解析器可执行程序,并把脚本本身作为参数传递给他。
注意
- 如果shell脚本的命令行中已经有脚本解析器了,那么脚本中的"#!"内容将被丢弃,而采用命令行中的解析器。
例如
$ /bin/tcsh a.sh
/bin/tcsh
$ /usr/local/bin/pdksh a.sh
/usr/local/bin/pdksh
$ /bin/ksh a.sh
/bin/readlink
- 如果shell脚本中没有魔数"#!"怎么办
shell使用当前的shell来处理。
$ cat a.sh
readlink /proc/$$/exe
$ ./a.sh
/bin/bash
$ echo $SHELL
/bin/bash
- 对于一般文件的加载过程
$ ./file格式
当前shell会读取文件file的魔数
- 如果是".ELF",二进制可执行程序,安装ELF文件处理
- 区分.o, .so, etc.
- 如果是"#!",那么加载魔数后面指定的可执行程序,并把file作为参数传递过去。
- 其它:file会作为一个当前shell类型的脚本运行,即试图把path_to_file的内容按照脚本来解释执行。
- sh file格式
注意这种格式要求file必须是一个shell脚本,不能是二进制可执行程序。
如果file是一个二进制格式文件,则可以采用格式:sh -c file
对于后一种格式,其实不管file是一个脚本还是一个二进制可执行程序,都可以运行。
我也不清楚为什么sh file格式不能支持file是二进制可执行程序,理论上sh还是可以去分析file的魔数,从而判断file的类型,然后做区分处理。