Command-Line

使用引號的命令差異(查找)

  • January 15, 2019

我正在閱讀手冊頁,find發現自己對以下命令感到困惑。一個和它對應的一個有什麼區別。

  1. 以下兩個命令有什麼區別:
find -execdir command "{}" \;
find -execdir "command {}" \;

混淆的原因:我認為引用應該指示外殼將引用的部分作為一個塊。因此,當我看到第二個時,我認為它會失敗,因為沒有命令command <file-name>

  1. 以下兩者有什麼區別:
find -execdir bash -c "command" "{}" \;
find -execdir bash -c "command {}" \;

混淆的原因:根據我的理解,在第二個版本中將命令與花括號一起括起來應該作為一個整體傳遞給 bash 命令,並且find不應該用相應的文件名解釋大括號。

  1. 以下兩者有什麼區別:
find -execdir bash -c "something \"$@\"" {} \;
find -execdir bash -c 'something "$@"' bash {} \;

據我了解,兩者是相同的。將大括號傳遞給 shell,第二個版本,如何比第一個更安全。

更新

剛剛注意到問題#3 的第一個版本的命令不起作用!嘗試了以下(沒有工作):

find -execdir bash -c 'something \"$@\"' {} \; # changed external quotes to single ones.
find -execdir bash -c "something $@" {} \; # removed the inner quotes.
find -execdir bash -c 'something $@' {} \; # same as above but with outer single quotes instead.

我在這裡想念什麼?我相信我應該能夠將大括號從包含命令的引號中去掉?

這(正如您所注意到的)相當複雜。我會試著解釋一下。考慮命令經過的解析/處理順序並觀察每一步發生的情況是有幫助的。通常,該過程如下所示:

  1. shell 解析命令行,將其分解為標記(“單詞”),替換變數引用等,並刪除引號和轉義符(在應用它們的效果之後)。然後它(通常)執行第一個“單詞”作為命令名稱(在這些情況下為“查找”),並將其餘單詞作為參數傳遞給它。
  2. find搜尋文件,並將其“ -execdir”和“ ;”之間的內容作為命令執行。請注意,它將“ {}”替換為匹配的文件名,但不進行其他解析——它只是執行“ -execdir”之後的第一個 arg 作為命令名,並將以下參數作為參數傳遞給它。
  3. 如果該命令恰好是bash並且它被傳遞了-c選項,它會立即將參數解析-c為命令字元串(有點像微型 shell 腳本),並將其其餘參數作為該迷你腳本的參數。

好的,在我深入研究之前還有一些其他注意事項:我正在使用 BSD find,它要求明確指定要搜尋的目錄,所以我將使用find . -execdir ...而不是find -execdir .... 我在一個包含文件“foo.txt”和“z@$%^;*;echo wheee.jpg”的目錄中(以說明使用bash -c錯誤的風險)。最後,我pargs在我的二進制目錄中呼叫了一個簡短的腳本,它列印了它的參數(或者如果沒有得到任何參數就會抱怨)。

問題一:

現在讓我們試試第一個問題中的兩個命令:

$ find . -execdir pargs "{}" \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^;*;echo wheee.jpg'
$ find . -execdir "pargs {}" \;
find: pargs .: No such file or directory
find: pargs foo.txt: No such file or directory
find: pargs z@$%^;*;echo wheee.jpg: No such file or directory

這符合您的期望:第一個有效(順便說一句,您可以省略雙引號{}),第二個失敗,因為空格和文件名被視為命令名稱的一部分,而不是它的參數。

順便說一句,也可以使用-exec[dir] ... +而不是-exec[dir] \;– 這告訴find盡可能少地執行命令,並一次傳遞一堆文件名:

$ find . -execdir pargs {} +
pargs got 3 argument(s): '.' 'foo.txt' 'z@$%^;*;echo wheee.jpg'

問題二:

這次我將一次選擇一個選項:

$ find . -execdir bash -c "pargs" "{}" \;
pargs didn't get any arguments
pargs didn't get any arguments
pargs didn't get any arguments

“嗯”,你說?這裡發生的是使用“ ”、“ ”、“ ”之bash類的參數列表執行。該選項告訴執行它的下一個參數(“pargs”),就像一個微型 shell 腳本,像這樣:-c``pargs``foo.txt``-c``bash

#!/bin/bash
pargs

…然後將“迷你腳本”傳遞給參數“foo.txt”(稍後會詳細介紹)。但是那個迷你腳本並沒有對它的參數做任何事情——具體來說,它不會將它們傳遞給pargs命令,所以pargs什麼都看不到。(我將在第三個問題中找到正確的方法。)現在,讓我們嘗試第二個問題的第二個替代方案:

$ find . -execdir bash -c "pargs {}" \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^'
bash: foo.txt: command not found
wheee.jpg

現在一切都在起作用,但只是在某種程度上。bash使用參數“ -c”和“pargs”+文件名執行,這與“。”的預期相同。和“foo.txt”,但是當你傳遞bash參數“ -c”和“ pargs z@$%^;*;echo wheee.jpg”時,它現在執行相當於它的迷你腳本:

#!/bin/bash
pargs z@$%^;*;echo wheee.jpg

因此 bash 會將其拆分為三個用分號分隔的命令:

  1. pargs z@$%^”(你看到的效果)
  2. " *",擴展為單詞 " foo.txt" 和 " z@$%^;*;echo wheee.jpg",因此嘗試foo.txt作為命令執行並將另一個文件名作為參數傳遞給它。沒有該名稱的命令,因此它給出了適當的錯誤。
  3. echo echo wheee.jpg”,這是一個非常合理的命令,正如你所見,它會在終端上列印“wheee.jpg”。

因此它適用於具有純名稱的文件,但是當它遇到包含 shell 語法的文件名時,它開始嘗試執行部分文件名。這就是為什麼這種做事方式不被認為是安全的。

問題三:

同樣,我將一次查看一個選項:

$ find . -execdir bash -c "pargs \"$@\"" {} \;
pargs got 1 argument(s): ''
pargs got 1 argument(s): ''
pargs got 1 argument(s): ''
$ 

再一次,我聽到你說“嗯????” 這裡最大的問題是它$@沒有轉義或在單引號中,因此它在傳遞find. 我將在這裡pargs展示find實際得到的參數:

$ pargs . -execdir bash -c "pargs \"$@\"" {} \;
pargs got 7 argument(s): '.' '-execdir' 'bash' '-c' 'pargs ""' '{}' ';'

請注意,$@剛剛消失了,因為我在一個沒有收到任何參數(或使用set命令設置它們)的互動式 shell 中執行它。因此,我們正在執行這個迷你腳本:

#!/bin/bash
pargs ""

…這解釋了為什麼pargs得到一個空參數。

如果這是在接收參數的腳本中,事情會更加混亂。轉義(或單引號)$解決了這個問題,但仍然不太有效:

$ find . -execdir bash -c 'pargs "$@"' {} \;
pargs didn't get any arguments
pargs didn't get any arguments
pargs didn't get any arguments

這裡的問題是bash將迷你腳本之後的下一個參數視為迷你腳本的名稱(迷你腳本可用作$0,但包含在 中$@),而不是正常參數(即$1)。這是一個展示這個的正常腳本:

$ cat argdemo.sh 
#!/bin/bash
echo "My name is $0; I received these arguments: $@"
$ ./argdemo.sh foo bar baz
My name is ./argdemo.sh; I received these arguments: foo bar baz

現在用一個類似bash -c的小腳本試試這個:

$ bash -c 'echo "My name is $0; I received these arguments: $@"' foo bar baz
My name is foo; I received these arguments: bar baz

解決這個問題的標準方法是添加一個虛擬腳本名稱參數(如“bash”),以便實際參數以通常的方式顯示:

$ bash -c 'echo "My name is $0; I received these arguments: $@"' mini-script foo bar baz
My name is mini-script; I received these arguments: foo bar baz

這正是您的第二個選項所做的,將“bash”作為腳本名稱傳遞,找到的文件名傳遞為$1

$ find . -execdir bash -c 'pargs "$@"' bash {} \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^;*;echo wheee.jpg'

這終於奏效了——真的,即使是奇怪的文件名。這就是為什麼這(或您的第一個問題中的第一個選項)被認為是使用find -exec[dir]. 您也可以將其與以下-exec[dir] ... +方法一起使用:

$ find . -execdir bash -c 'pargs "$@"' bash {} +
pargs got 3 argument(s): '.' 'foo.txt' 'z@$%^;*;echo wheee.jpg'

引用自:https://unix.stackexchange.com/questions/493808