所有現今的UNIX系統都支持解釋器文件(interPReter file)(也可稱為解釋器腳本)。這種文件是文本文件,其起始行格式是:
#! pathname [optional-argument]
感嘆號和pathname之間的空格是可選的。最常見的解釋器文件以下列行開始:
#!/bin/sh
pathname通常是絕對路徑名,對它不進行什么特殊的處理(即不使用PATH進行路徑搜索)。對這種文件的識別是由內核作為exec系統調用處理的一部分來完成的。內核調用exec函數的進程實際執行的并不是該解釋器文件,而是該解釋器文件第一行中pathname所指定的文件。一定要將解釋器文件(文本文件,它以#!開頭)和解釋器(由該解釋器文件第一行中的pathname指定)區分開來。(我們可以認為:解釋器文件就是在首行指定了其所使用的解釋器的文本文件。)
要知道很多系統對解釋器文件的第一行有長度限制。這些限制包括#!、pathname、可選參數、終止換行符以及空格數。在linux中支持該限制為127字節。
實例
讓我們觀察一個實例,從中可了解當被執行的文件是解釋器文件時,內核如何處理exec函數的參數及該解釋器文件第一行的可選參數。程序清單8-10調用exec執行一個解釋器文件。
程序清單8-10 執行一個解釋器文件的程序
[root@localhost apue]# cat prog8-10.c#include "apue.h"#include <sys/wait.h>intmain(void){ pid_t pid; if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid == 0) /* child */ { if(execl("/home/zhu/apue/testinterp", "testinterp", "myarg1", "MY ARG2", (char *)0) < 0) err_sys("execl error"); } if(waitpid(pid, NULL, 0) < 0) /* parent */ err_sys("waitpid error"); exit(0);}
程序清單8-10中被執行的解釋器文件內容如下(只有一行):
[root@localhost apue]# cat testinterp#!/home/zhu/apue/echoarg foo
程序echoarg(解釋器)回送每一個命令行參數(它就是程序清單7-3,參見:http://www.CUOXin.com/nufangrensheng/p/3507949.html)。
程序清單8-10運行結果:
[root@localhost apue]# ./prog8-10argv[0]: /home/zhu/apue/echoargargv[1]: fooargv[2]: /home/zhu/apue/testinterpargv[3]: myarg1argv[4]: MY ARG2
注意,當內核exec該解釋器(/home/zhu/apue/echoarg)時,argv[0]是該解釋器的pathname,argv[1]是解釋器文件中的可選參數,其余參數是pathname(/home/zhu/apue/testinterp),以及程序清單8-10中調用execl的第二個和第三個參數(myarg1和MY ARG2)。調用execl時的argv[1]和argv[2]已右移了兩個位置(?)。注意,內核取execl調用中的pathname而非第一個參數(testinterp),因為一般而言,pathname包含了比第一個參數更多的信息(通常第一個參數只是pathname的一部分)。
總的來說,當內核exec解釋器時,其命令行參數依次是該解釋器的pathname、解釋器的可選參數、解釋器文件的pathname、exec函數調用參數列表中argv[0]以后的參數(不包括argv[0],通常argv[0]只是解釋器文件pathname中的一部分)。(http://www.CUOXin.com/nufangrensheng/p/3510821.html中有提到我們可將argv[0]設置為任何字符串。)
另外還有一篇關于解釋器和解釋器文件比較簡明扼要的一篇文章,可參考:http://www.CUOXin.com/beacer/archive/2012/09/16/2687659.html
實例
在解釋器pathname后可跟隨可選參數。如果一個解釋器程序支持-f選項,那么在pathname后經常使用的可選參數就是-f。
例如,可以以下列方式執行awk(1)程序:
awk -f myfile
它告訴awk從文件myfile中讀取awk程序。
如果在解釋器文件中使用-f選項,則可以寫成:
#!/bin/awk -f......
如果我們在myfile的第一行寫:#!/bin/awk -f,那么我們想要執行awk程序myfile時,不必再輸入命令awk –f myfile,而是直接輸入命令myfile即可。
例如,程序清單8-11為/usr/local/bin/awkexample這樣一個解釋器文件。
程序清單8-11 作為解釋器文件的awk程序
#!/bin/awk -fBEGIN { for(i=0; i<ARGC; i++) printf "ARGV[%d] = %s/n", i, ARGV[I] exit}
如果路徑前綴之一是/usr/local/bin,則可以用下列方式執行程序清單8-11:
[root@localhost apue]# awkexample file1 FILENAME2 f3ARGV[0] = awkARGV[1] = file1ARGV[2] = FILENAME2ARGV[3] = f3
其實,shell會這樣調用exec:
exec("/usr/local/bin/awkexample", "awkexample", "file1", "FILENAME2", "f3", NULL);
但是,實際上exec執行的是解釋器/bin/awk而不是解釋器文件awkexample,并且其命令行參數是:
/bin/awk -f /usr/local/bin/awkexample file1 FILENAME2 f3
是否一定需要解釋器文件呢?那也不完全如此。但是它們確實使用戶得到效率方面的好處,其代價是內核的額外開銷(因為識別解釋器文件的是內核)。
由于下述理由,解釋器文件是有用的:
(1)有些程序是用某種語言編寫的腳本,解釋器文件可將這一事實隱藏起來。例如,為了執行程序8-11,只需使用下列命令行:
awkexample opitonal-arguments
而并不需要知道該程序實際上是一個awk腳本,否則就需要以下列方式執行該程序:
awk -f awkexample optional-arguments
(2)解釋器腳本在效率方面也提供了好處。通常,用一個shell腳本代替解釋器腳本需要更多的開銷。
(3)解釋器腳本使我們可以使用除/bin/sh以外的其他shell來編寫shell腳本。(當execlp找到一個非機器可執行的可執行文件時(shell腳本是一個可執行文件,但卻不是機器可執行的),它總是調用/bin/sh來解釋執行該文件。)此時,我們只需要在解釋器腳本首行指明所用的shell即可。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答