題 當文件名中包含空格時,如何解析find命令的輸出?


使用諸如的循環

for i in `find . -name \*.txt` 

如果某些文件名中包含空格,則會中斷。

我可以用什麼技術來避免這個問題?


11
2018-04-07 22:59


起源


請注意,文件的文件名也可以包含換行符。這就是原因 find -print0 和 xargs -0。 - Daniel Beck♦


答案:


理想情況下,你根本不這樣做,因為在shell腳本中正確解析文件名總是很困難(修復它用於空格,你仍然會遇到其他嵌入字符的問題,特別是換行符)。這甚至被列為 第一次進入 在BashPitfalls頁面中。

也就是說,有一種方法幾乎可以做你想要的:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

記得還要引用 $i 在使用它時,為了避免以後解釋空間的其他事情。還記得設置 $IFS 使用它後回來,因為不這樣做會導致以後出現令人眼花繚亂的錯誤。

這確實有另一個警告:內部發生了什麼 while 循環可以在子shell中進行,具體取決於您使用的確切shell,因此變量設置可能不會持久存在。該 for 循環版避免了這種情況,但即使你申請了,也要付出代價 $IFS 解決方案,以避免空格問題,如果你將遇到麻煩 find 返回太多文件。

在某些時候,所有這一切的正確修復都變成了用Perl或Python等語言而不是shell。


11
2018-04-07 23:14



我喜歡用Python來避免這一切的想法。 - Scott C Wilson


使用 find -print0 把它管道 xargs -0,或編寫自己的小C程序並將其傳遞給您的小C程序。這是什麼 -print0 和 -0 被發明了。

Shell腳本不是處理帶有空格的文件名的最佳方法:你可以做到,但它變得笨重。


11
2018-04-08 01:17



在我的機器上工作^ TM! - mcandre


您可以設置“內部字段分隔符”(IFS)用於循環參數分割的空間而不是空間,例如,

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

我重置了 IFS 在使用它之後,主要是因為它看起來不錯,我想。我沒有看到將它設置為換行符的任何問題,但我認為這是“更清潔”。

另一種方法,取決於你想要對輸出做什麼 find,是要么直接使用 -exec 隨著 find 命令或使用 -print0 把它管成 xargs -0。在第一種情況下 find 負責文件名轉義。在裡面 -print0 案件, find 使用空分隔符打印其輸出,然後 xargs 分裂這個。由於沒有文件名可以包含該字符(我所知道的),因此這也是安全的。這在簡單的情況下大多有用;並且通常不是一個完整的替代品 for 環。


2
2018-04-07 23:13





運用 find -print0 同 xargs -0

運用 find -print0 結合 xargs -0 對於合法文件名是完全健壯的,並且是可用的最可擴展方法之一。例如,假設您想要列出當前目錄中的每個PDF文件。你可以寫

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

這將找到每個PDF(通過 -iname '*.pdf')在當前目錄中(.)和任何子目錄,並將它們作為參數傳遞給 echo 命令。因為我們指定了 -n 1 選項, xargs只會一次傳遞一個參數 echo。如果我們省略了該選項, xargs 會盡可能多地通過 echo。 (您可以 echo short input | xargs --show-limits 查看命令行中允許的字節數。)

是什麼 xargs 做到了嗎?

我們可以清楚地看到效果 xargs 有它的輸入 - 和的影響 -n 特別是 - 通過使用一個腳本,它以更精確的方式回應其參數 echo

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

請注意,它可以很好地處理空格和換行符,

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

使用以下常見解決方案會特別麻煩:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
筆記

1
2017-08-22 19:34





我不同意 bash 抨擊者,因為 bash與* nix工具集一起,非常擅長處理文件(包括名稱嵌入了空格的文件)。

其實, find 讓你對選擇要處理的文件進行細緻的控制......在bash方面,你真的只需要意識到你必須讓你成為一個字符串 bash words;通常使用“雙引號”,或其他一些機制,如使用IFS或find {} 

請注意,在大多數情況下,您不需要設置和重置IFS;只需在本地使用IFS,如下例所示。這三個處理空白都很好。此外,您不需要“標準”循環結構,因為 找到的  \;   有效地循環;只需將循環邏輯放入bash函數(如果您沒有調用標準工具)。

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

還有兩個例子

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'找also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+instead\;`)


1
2018-04-08 10:25



兩種觀點都有一定的效力。當我只處理我自己的文件時,我會使用find而不用擔心它,因為我的文件名稱中沒有空格(或回車!)。但是當你開始使用其他人的文件時,你必須使用更強大的技術。 - Scott C Wilson