2012-10-08 45 views
5

大家都說eval是邪惡的,你應該用$()作爲替代。但是我遇到了沒有在$()中處理相同的處理的情況。

背景是,我被它們中的空間文件路徑燒得太頻繁,所以想引用所有這些路徑。想要知道我的所有可執行文件來自何處的更多偏執狂。更偏執,不信任自己,就像能夠顯示我即將運行的創建命令一樣。

下面我嘗試變化上使用eval與$(),和命令名是否被引用(因爲真的可以包含空格)

BIN_LS="/bin/ls" 
    thefile="arf" 
    thecmd="\"${BIN_LS}\" -ld -- \"${thefile}\"" 

    echo -e "\n Running command '${thecmd}'" 
    $($thecmd) 

      Running command '"/bin/ls" -ld -- "arf"' 
     ./foo.sh: line 8: "/bin/ls": No such file or directory 

    echo -e "\n Eval'ing command '${thecmd}'" 
    eval $thecmd 

      Eval'ing command '"/bin/ls" -ld -- "arf"' 
     /bin/ls: cannot access arf: No such file or directory 

    thecmd="${BIN_LS} -ld -- \"${thefile}\"" 

    echo -e "\n Running command '${thecmd}'" 
    $($thecmd) 

      Running command '/bin/ls -ld -- "arf"' 
     /bin/ls: cannot access "arf": No such file or directory 

    echo -e "\n Eval'ing command '${thecmd}'" 
    eval $thecmd 

      Eval'ing command '/bin/ls -ld -- "arf"' 
     /bin/ls: cannot access arf: No such file or directory 

    $("/bin/ls" -ld -- "${thefile}") 

     /bin/ls: cannot access arf: No such file or directory 

所以...這是令人困惑的。引用的命令路徑在除了$()構造之外的任何地方都是有效的?更短,更直接的例子:

$ c="\"/bin/ls\" arf" 
$ $($c) 
-bash: "/bin/ls": No such file or directory 
$ eval $c 
/bin/ls: cannot access arf: No such file or directory 
$ $("/bin/ls" arf) 
/bin/ls: cannot access arf: No such file or directory 
$ "/bin/ls" arf 
/bin/ls: cannot access arf: No such file or directory 

如何解釋簡單​​情況?

回答

3

事實上,它首先是沒有意義的。 Use an array instead.

$ c=("/bin/ls" arf) 
$ "${c[@]}" 
/bin/ls: cannot access arf: No such file or directory 
+0

未加引號的字符串會立即進行評估。 c =(「/ bin/ls」/ bin/l *)與想要的完全不同。 (用echo「'$ {c [@]}'」)c =(「/ bin/ls」「/ bin/l *」)然後z = $(「$ {c [@]}」)失敗。 $()不能很好地使用參數數組? – Shenme

+1

然而,c =(「/ bin/ls」「/ bin/l *」)然後z = $($ {c [@]})似乎工作得很好。這是你想給我看的可執行形式$()嗎? – Shenme

5

採用"引用的話是砸向你互動的一部分。當你在提示符下鍵入

$ "/bin/ls" arf 

,或者在腳本中,你告訴猛砸該命令包含文字/bin/lsarf,和雙引號確實強調/bin/ls是一個字。

當你鍵入

$ eval '"/bin/ls" arf' 

你告訴猛砸該命令包含文字eval"/bin/ls" arf。由於eval目的是假裝它的參數是一個實際的人的輸入命令,這等同於運行

$ "/bin/ls" arf 

"得到處理就像在提示。

請注意,這個僞裝是專門針對eval; Bash通常不會假裝某種東西是真正的人類命令。

當你鍵入

$ c='"/bin/ls" arf' 
$ $c 

$c被取代,然後進行分詞(見§3.5.7 "Word Splitting" in the Bash Reference Manual),因此命令的話"/bin/ls"(注意雙引號!)和arf。不用說,這是行不通的。 (它也不是很安全,因爲除了分詞之外,$c也經歷了文件名擴展以及什麼都沒有。通常你的參數擴展應該總是用雙引號,如果它們不能,那麼你應該重寫你的代碼,以便它們可以。未加引號的參數擴展正在尋求麻煩。)

當你鍵入

$ c='"/bin/ls" arf' 
$ $($c) 

這是和以前一樣,但現在你也嘗試使用非工作命令的輸出作爲命令。不用說,這不會導致非工作命令突然工作。

正如伊格納西奧巴斯克斯 - 艾布拉姆斯說,在他的回答中,正確的解決方案是使用一個數組,並妥善處理報價:

$ c=("/bin/ls" arf) 
$ "${c[@]}" 

這臺c到一個數組有兩個元素,/bin/lsarf,並將這兩個元素用作命令的單詞。

+0

(討厭5分鐘的限制)最後一行沒有顯示可以捕獲輸出的形式的封裝命令的執行(雖然在原始問題中沒有說明是必需的)嘗試c =(「/ bin/ls」「/ bin/l *「)和 c =(」/ bin/ls「/ bin/l *)with echo」'$ {c [@]}'「。這個方法不會讓我用帶引號的參數建立一個數組,以便以後使用$()...? – Shenme

+1

@ Shenhen:據我所知,你說的是,「我想用'eval',因爲我想處理一個任意字符串作爲Bash命令。」其他人所說的是,「'eval'是邪惡的,因爲它將任意字符串處理爲Bash命令。「你試圖調和這些觀點,問道:」有沒有一種非主動的方式來做這件壞事?「,但這種做法是行不通的,這些觀點根本上是不可調和的。一個邪惡的工具;或者,爲了避免邪惡的工具,重寫你的腳本以避免做壞事 – ruakh

+0

不,沒有任何命令,沒有惡意:-)只是想建立一個命令並以受控方式執行它。我會控制輸入,甚至有些偏執地使用' - '。人們看到複雜的元問題的簡單回答,並批評你,如果你'使用eval',所以我想避免批評。 - (無論如何,z = $($ {c [@]})工作得很好 – Shenme

2

man page for bash,關於eval

EVAL [ARG ...]: 的ARGS被讀取並連接在一起成一個單一的命令。 然後該命令由shell讀取並執行,並且其退出 狀態作爲eval的值返回。

c時被定義爲"\"/bin/ls\" arf",外引號將導致整個事情要被處理的第一個參數到eval,其預計將命令或程序。您需要傳遞eval參數,以便將target命令及其參數單獨列出。

$(...)構造的行爲不同於eval,因爲它不是一個帶參數的命令。它可以一次處理整個命令,而不是一次處理一個參數。

關於您的原始前提的說明:人們說eval是邪惡的主要原因是因爲它通常被腳本用作執行用戶提供的字符串作爲shell命令。雖然有時,這是一個主要安全問題(通常沒有實際的方法來安全檢查字符串執行之前)。如果您在腳本內的硬編碼字符串上使用eval,則安全問題不適用。但是,在腳本內部使用$(...)`...`進行命令替換通常更簡單,更簡潔,因此沒有留下eval的實際使用情況。

+0

附加提示:當試圖查看shell如何看到擴展命令時,使用'set -v'有時可以給出比'echo'更準確的結果。 – bta

+0

所以基本上,如果評估字符串不以任何方式暴露給用戶輸入,'eval'是安全的;但如果我們在任何時候都無法阻止這種情況發生,它可以被利用;因此使用''''''或'$(...)'與'eval'相比需要更少的預防措施; thx男人!這是我在更改腳本之前尋找的特定提示,呵呵! :) –