2011-08-12 148 views
153

我想遍歷文件列表。這份名單是find命令的結果,所以我想出了:使用空格遍歷文件列表

getlist() { 
    for f in $(find . -iname "foo*") 
    do 
    echo "File found: $f" 
    # do something useful 
    done 
} 

它的罰款,除非文件在其名稱中的空格:

$ ls 
foo_bar_baz.txt 
foo bar baz.txt 

$ getlist 
File found: foo_bar_baz.txt 
File found: foo 
File found: bar 
File found: baz.txt 

我能做些什麼,以避免分裂在空間上?

回答

177

你可以替換爲基於詞的迭代基於行的一個:

find . -iname "foo*" | while read f 
do 
    # ... loop body 
done 
+26

這是非常乾淨。並且讓我感覺比改變IFS和for循環更合適 – Derrick

+13

這將拆分包含\ n的單個文件路徑。好吧,那些不應該在周圍,但他們可以創建:'touch「$(printf」foo \ nbar「)」' –

+2

@OllieSaunders:它總是一些東西! –

11
find . -name "fo*" -print0 | xargs -0 ls -l 

請參閱man xargs

12
find . -iname "foo*" -print0 | xargs -L1 -0 echo "File found:" 
+5

只有在你想執行一個命令時才能使用。 shell內置函數不會以這種方式工作。 – Alex

141

有幾種可行的方式來實現這一目標。

如果要密切堅持你原來的版本,它可以做到這樣:

getlist() { 
     IFS=$'\n' 
     for file in $(find . -iname 'foo*') ; do 
       printf 'File found: %s\n' "$file" 
     done 
} 

這仍然會失敗,如果文件名在他們的文字換行,但空間不會打破它。

但是,與IFS混合是沒有必要的。這裏是我做到這一點首選方式:

getlist() { 
    while IFS= read -d $'\0' -r file ; do 
      printf 'File found: %s\n' "$file" 
    done < <(find . -iname 'foo*' -print0) 
} 

如果您發現< <(command)語法不熟悉你應該閱讀有關process substitution。與for file in $(find ...)相比,此優點是可以正確處理包含空格,換行符和其他字符的文件。這是可行的,因爲find-print0將使用null(又名\0)作爲每個文件名的終止符,並且與換行符不同,null不是文件名中的合法字符。

的優勢,這在幾乎等效的版本

getlist() { 
     find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do 
       printf 'File found: %s\n' "$file" 
     done 
} 

的是,在while循環體的任何變量賦值被保留。也就是說,如果你像上面那樣輸入到while那麼while的主體在一個子shell中,這可能不是你想要的。

的進程替換版本在find ... -print0 | xargs -0的優點是最小的:該xargs版本是好的,如果你需要的是打印線或對文件執行一個操作,但如果你需要執行多個步驟的循環版本更容易。

編輯:這是一個很好的測試腳本,這樣你就可以在解決這個問題

#!/usr/bin/env bash 

dir=/tmp/getlist.test/ 
mkdir -p "$dir" 
cd "$dir" 

touch  'file not starting foo' foo foobar barfoo 'foo with spaces'\ 
    'foo with'$'\n'newline 'foo with trailing whitespace  ' 

# while with process substitution, null terminated, empty IFS 
getlist0() { 
    while IFS= read -d $'\0' -r file ; do 
      printf 'File found: '"'%s'"'\n' "$file" 
    done < <(find . -iname 'foo*' -print0) 
} 

# while with process substitution, null terminated, default IFS 
getlist1() { 
    while read -d $'\0' -r file ; do 
      printf 'File found: '"'%s'"'\n' "$file" 
    done < <(find . -iname 'foo*' -print0) 
} 

# pipe to while, newline terminated 
getlist2() { 
    find . -iname 'foo*' | while read -r file ; do 
      printf 'File found: '"'%s'"'\n' "$file" 
    done 
} 

# pipe to while, null terminated 
getlist3() { 
    find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do 
      printf 'File found: '"'%s'"'\n' "$file" 
    done 
} 

# for loop over subshell results, newline terminated, default IFS 
getlist4() { 
    for file in "$(find . -iname 'foo*')" ; do 
      printf 'File found: '"'%s'"'\n' "$file" 
    done 
} 

# for loop over subshell results, newline terminated, newline IFS 
getlist5() { 
    IFS=$'\n' 
    for file in $(find . -iname 'foo*') ; do 
      printf 'File found: '"'%s'"'\n' "$file" 
    done 
} 


# see how they run 
for n in {0..5} ; do 
    printf '\n\ngetlist%d:\n' $n 
    eval getlist$n 
done 

rm -rf "$dir" 
+1

接受你的答案:最完整和有趣的 - 我不知道'$ IFS'和'<<(cmd)'語法。還有一件事對我來說依然晦澀難懂,爲什麼'$'\ 0''中的'$'?非常感謝。 – gregseth

+0

@gregseth:這是字面轉義字符的bash語法。例如,如果您說CTRL + V,然後點擊TAB,則插入一個文字標籤。但是,當複製並粘貼到其他地方時,這看起來不正確,但語法'$'\ t''將作爲選項卡進行評估,並且工作方式相同。這只是將某些字符傳遞給命令的一種便捷方式,而不用擔心shell將它們弄糟。 – Sorpigal

+0

好的。用我的第一篇文章,我沒有想到對我的未解決的問題得到很多答案;) - 再次感謝。 – gregseth

24

還有一個非常簡單的解決方案得到不同的嘗試之間的差異的一個想法:依靠bash的通配符

$ mkdir test 
$ cd test 
$ touch "stupid file1" 
$ touch "stupid file2" 
$ touch "stupid file 3" 
$ ls 
stupid file 3 stupid file1  stupid file2 
$ for file in *; do echo "file: '${file}'"; done 
file: 'stupid file 3' 
file: 'stupid file1' 
file: 'stupid file2' 

請注意,我不確定此行爲是默認的行爲,但我沒有在我的shopt中看到任何特殊設置,所以我會去說它應該是「安全的」(在osx和ubuntu上測試)。

+1

''$ {file}「'語法爲我創造了奇蹟。 –

+3

最佳答案!這似乎比其他多命令嘗​​試更簡潔。 –

+3

簡單而乾淨。如果可以的話,我會給它兩個upvotes。 –

6

因爲你沒有做任何其他類型的過濾與find,您可以使用以下爲bash 4.0:

shopt -s globstar 
getlist() { 
    for f in **/foo* 
    do 
     echo "File found: $f" 
     # do something useful 
    done 
} 

**/將匹配零個或多個目錄,因此,整個模式將匹配foo*在當前目錄或任何子目錄中。

0

在某些情況下,如果您只是需要複製或移動文件列表,您也可以將該列表管理爲awk。
重要的\"" "\"圍繞字段$0(簡而言之,你的文件,一個行列表=一個文件)。

find . -iname "foo*" | awk '{print "mv \""$0"\" ./MyDir2" | "sh" }' 
1

我真的很喜歡for循環和數組迭代,所以我想我會這個答案加進來......

我也喜歡marchelbling的愚蠢文件示例。 :)

$ mkdir test 
$ cd test 
$ touch "stupid file1" 
$ touch "stupid file2" 
$ touch "stupid file 3" 

內部測試目錄:

readarray -t arr <<< "`ls -A1`" 

這增加了每個文件列出一行到名爲arr去除任何換行符一個bash數組。

比方說,我們希望給這些文件更好的名字...

for i in ${!arr[@]} 
do 
    newname=`echo "${arr[$i]}" | sed 's/stupid/smarter/; s/ */_/g'`; 
    mv "${arr[$i]}" "$newname" 
done 

$ {!常用3 [@]}擴展到0 1 2所以 「$ {改編[$ i]}」 是i th該陣列的元素。變量周圍的引號對於保存空間很重要。

結果爲三名命名的文件:

$ ls -1 
smarter_file1 
smarter_file2 
smarter_file_3