2016-05-21 30 views
4

我想寫一個非常仔細地模仿argv [0]/$ 0的值的bash包裝腳本。我使用exec -a來執行一個單獨的程序,其中包含argv [0]的值。我發現有時bash的$ 0不會給出我在C程序的argv [0]中獲得的相同值。以下是一個演示用C和bash的區別簡單的測試程序:用bash仔細地模仿Argv [0]

int main(int argc, char* argv[0]) 
{ 
    printf("Argv[0]=%s\n", argv[0]); 
    return 0; 
} 

#!/bin/bash 
echo \$0=$0 

當與全(絕對或相對)路徑的二進制運行這些程序,它們的行爲相同:

$ /path/to/printargv 
Argv[0]=/path/to/printargv 

$ /path/to/printargv.sh 
$0=/path/to/printargv.sh 

$ to/printargv 
Argv[0]=to/printargv 

$ to/printargv.sh 
$0=to/printargv.sh 

但調用它們的時候,如果他們是在路徑中,我得到不同的結果:

$ printargv 
Arv[0]=printargv 

$ printargv.sh 
$0=/path/to/printargv.sh 

兩個問題:

1)這是預期的,可以解釋的行爲,或者這是一個錯誤? 2)什麼是「正確」的方式來實現精心模仿argv [0]的目標?

編輯:錯別字。

回答

2

你看到的這裏是bashexecve的記錄的行爲(至少,它記錄上LinuxFreeBSD;我相信,其他系統也有類似的文件),並反映了argv[0]構造的不同方式。

Bash(像任何其他shell)構造argv從提供的命令行,在執行了各種擴展,根據需要resplit字等等之後。最終的結果是,當在鍵入

printargv 

argv被構造爲{ "printargv", NULL }和當鍵入

to/printargv 

argv被構造爲{ "to/printargv", NULL }。所以沒有什麼驚喜。

(在這兩種情況下,都出現過命令行參數,他們會出現在argv開始在位置1)

但執行路徑分叉,這一點上。當命令行中的第一個單詞包含/時,則認爲它是文件名,無論是相對的還是絕對的。殼不做進一步處理;它簡單地使用提供的文件名作爲filename參數和argv陣列作爲其argv參數構造的簡單號碼execve。在這種情況下,argv[0]正好對應於filename

但是,當命令沒有斜槓:

printargv 

殼做了很多工作:

  • 首先,它會檢查如果該名稱是用戶定義的shell函數。如果是這樣,它會執行它,$1...$n取自argv陣列已經構建。 ($0仍然是腳本調用的argv[0]

  • 然後,它檢查名稱是否是內置的bash命令。如果是這樣,它會執行它。內置函數如何與命令行參數進行交互不在這個答案的範圍之內,並且實際上並不是用戶可見的。

  • 最後,它試圖通過搜索$PATH的組件並查找可執行文件來查找與該命令對應的外部實用程序。如果它找到一個,它會調用execve,給它一個它作爲filename參數找到的路徑,但仍然使用由命令中的單詞組成的argv數組。所以在這種情況下,filenameargv[0]不是對應。

因此,在兩種情況下,殼結束調用execve,提供了一個文件路徑(可能相對)作爲filename參數和字分割命令作爲argv參數。

如果指定的文件是可執行映像文件,沒有什麼可說的了,真的。圖像被加載到內存中,它的main被提供的argv矢量調用。 argv[0]將是一個單詞或相對或絕對路徑,僅取決於最初輸入的內容。

但是,如果指定的文件是腳本,加載器將產生錯誤,execve將檢查文件是否以shebang開頭(#!)。 (自2008年以來的Pos​​ix,execve也將嘗試運行該文件使用系統shell腳本,好像它有#!/bin/sh的家當線。)

下面是關於Linux的execve的文檔:

解釋型的腳本是具有執行啓用許可的文本文件,其第一行的形式爲:

 #! interpreter [optional-arg] 

的解釋必須是可執行文件的有效路徑名。如果調用execve的文件名參數()指定解釋型的腳本,然後解釋將與以下參數調用:

 interpreter [optional-arg] filename arg... 

其中arg...是該系列的話指出,由argv說法execve(),開始argv[1]

注意,在上文中,filename參數是filename參數execve。鑑於家當線#!/bin/bash我們現在有兩種

/bin/bash to/printargv   # If the original invocation was to/printargv 

/bin/bash /path/to/printargv  # If the original invocation was printargv 

注意argv[0]有效地消失了。

bash然後在文件中運行腳本。在執行該腳本之前,它將$0設置爲給定的文件名參數,在我們的示例中爲to/printargv/path/to/printargv,並將$1...$n設置爲其餘參數,這些參數是從原始命令行的命令行參數中複製的。

總之,如果你使用的文件名不帶斜槓調用命令:

  • 如果文件名包含可執行映像,它會看到argv[0]作爲命令名稱類型。

  • 如果文件名包含帶shebang行的bash腳本,則該腳本將看到$0作爲腳本文件的實際路徑。

如果調用使用帶斜槓的文件名的命令,在這兩種情況下,它會看到的argv [0]作爲文件名輸入號碼(這可能是相對的,但顯然會始終有一個斜槓)。另一方面,如果您通過顯式調用shell解釋器來調用腳本(bash printargv),腳本將會看到$0作爲鍵入的文件名,這不僅可能是相對的,也可能不會有斜槓。

所有這些意味着如果您知道調用您想要模仿的腳本的形式,只能「仔細地模仿argv [0]」。 (這也意味着腳本不應該依賴argv[0]的值,但這是一個不同的主題。)

如果您正在爲單元測試執行此操作,則應該提供一個選項來指定要提供的值爲argv [ 0]。許多試圖分析$0的shell腳本假定它是一個文件路徑。他們不應該這樣做,因爲它可能不是,但它就是這樣。如果你想抽出這些公用設施,你需要提供一些垃圾值,如$0。否則,作爲默認的最佳選擇是提供腳本文件的路徑。

+0

感謝您的回覆。 printargs.sh確實有一個shebang。我發佈了它的2行源代碼。 這仍然留下了問題的重要部分:什麼是準確模仿$ argv [0]與bash腳本的正確方法? –

+0

@jason,是的,對不起,我被打斷了。我會在幾個小時內完成答案。 – rici

+0

@jason:好的,重寫了答案;希望能幫助到你。我不認爲有一個「正確的方式」,因爲argv [0]可能是這個,或者可能是這樣,理想情況下,bash腳本也可以。所以如果你正在測試,你應該用$ 0的各種值測試腳本。如果你只是想給它一些合理的話,我的建議是使用完整的絕對文件名。 – rici