題 循環通過`ls`導致bash shell腳本


是否有人使用模板shell腳本來執行某些操作 ls 獲取目錄名稱列表並循環每個目錄名稱並執行某些操作?

我打算這樣做 ls -1d */ 獲取目錄名稱列表。


73
2017-08-28 17:03


起源




答案:


據編輯@ shawn-j-goff和其他人建議不要使用ls來表示。

只需使用一個 for..do..done 環:

for f in *; do
  echo "File -> $f"
done

你可以替換 * 同 *.txt 或者任何其他返回列表的文件(文件,目錄或其他任何內容),一個生成列表的命令,例如, $(cat filelist.txt),或實際用列表替換它。

do 循環,你只需用美元符號前綴引用循環變量(所以 $f 在上面的例子中)。您可以 echo 它或者你想做的任何其他事情。

例如,要重命名所有 .xml 當前目錄中的文件 .txt

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

或者甚至更好,如果你使用Bash,你可以使用Bash參數擴展而不是產生子shell:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done

77
2017-08-28 17:09



如果文件名中有空格怎麼辦? - Daniel A. White
不幸的是,正如Daniel所說,如果任何文件或文件夾的名稱中包含空格或換行符,則此答案中的代碼將會中斷。它顯示了一種非常常見的誤用 for 循環,以及嘗試解析輸出的典型陷阱 ls。 @ DanielA.White,你 威力 如果沒有幫助(或可能具有誤導性),請考慮不接受這個答案,因為就像你說的那樣,你是在目錄上行事。 Shawn J. Goff的答案應該為您的問題提供更強大,更有效的解決方案。 - slhck
-∞ 你不應該解析 ls 產量, 你不應該讀取輸出 for 環,你應該使用 $() 而不是``和 使用更多報價。我確信@CoverosGene意味著好,但這太可怕了。 - l0b0
如果你真的想使用它是一個更好的選擇 ls 是 ls -1 | while read line; do stuff; done。至少那一個不會因為空白而破裂。 - Emmanuel Joubaud
同 mv -v 你不需要 echo "moved $x -> $t" - DmitrySandalov


使用輸出 ls 獲取文件名是個壞主意。它可能導致故障甚至危險的腳本。這是因為文件名可以包含除以外的任何字符 / 和 null性格和 ls 不會將這些字符中的任何一個用作分隔符,因此如果文件名有空格或換行符,則為  獲得意外的結果。

迭代文件有兩種非常好的方法。在這裡,我簡單地使用過 echo 演示用文件名做某事;但是你可以使用任何東西。

第一種是使用shell的原生通配功能。

for dir in */; do
  echo "$dir"
done

外殼擴展 */ 進入單獨的論點即 for 循環讀取;即使文件名中有空格,換行符或任何其他奇怪的字符, for 將每個完整的名稱視為一個原子單位;它沒有以任何方式解析列表。

如果你想遞歸進入子目錄,那麼除非你的shell有一些擴展的globbing特性(例如 bashglobstar。如果您的shell沒有這些功能,或者您想確保您的腳本可以在各種系統上運行,那麼下一個選項就是使用 find

find . -type d -exec echo '{}' \;

在這裡, find 命令會調用 echo 並傳遞一個文件名的參數。它為找到的每個文件執行一次此操作。與前面的示例一樣,沒有解析文件名列表;相反,fileneame完全作為參數發送。

的語法 -exec 爭論看起來有點好笑。 find 接下來的第一個參數 -exec 並將其視為要運行的程序,以及隨後的每個參數,它都作為參數傳遞給該程序。有兩個特殊的論點 -exec 需要看看。第一個是 {};這個參數被替換為前面部分的文件名 find 產生。第二個是 ;,讓 find知道這是傳遞給程序的參數列表的結尾; find 需要這個,因為你可以繼續使用更多的參數 find 而不是針對執行程序。之所以如此 \ 是殼也對待 ; 特別 - 它代表一個命令的結束,所以我們需要轉義它,以便shell將它作為參數 find 而不是為自己消費;讓shell不專門處理它的另一種方法是將它放在引號中: ';' 同樣有效 \; 以此目的。


63
2018-04-29 22:16



當您需要生成文件列表並在命令中使用它時,這絕對是一種方法。 find -exec僅受限於能夠運行單個命令。通過循環,您可以隨心所欲地管理內容。 - MaQleod
+1。我希望更多的人會使用 find。有魔力可以做,甚至可以做到的局限性 -exec 可以解決。該 -print0 選項對於使用也很有價值 xargs。 - ghoti


對於包含空格的文件,您必須確保引用變量,如:

 for i in $(ls); do echo "$i"; done; 

或者,您可以更改輸入字段分隔符(IFS)環境變量:

 IFS=$'\n';for file in $(ls); do echo $i; done

最後,根據你正在做的事情,你可能甚至不需要ls:

 for i in *; do echo "$i"; done;

7
2017-08-28 17:26



在第一個例子中很好地使用了子shell - Jeremy L
為什麼IFS在分配之後和新角色之前需要$? - Andy Ibanez
文件名也可以包含換行符。打破 \n 不夠。建議解析輸出是不是一個好主意 ls。 - ghoti


如果你有GNU Parallel http://www.gnu.org/software/parallel/ 安裝你可以這樣做:

ls | parallel echo {} is in this dir

要將所有.txt重命名為.xml:

ls *.txt | parallel mv {} {.}.xml

觀看GNU Parallel的介紹視頻了解更多信息: http://www.youtube.com/watch?v=OpaiGYxkSuQ


5
2017-08-22 20:02





只是為了添加CoverosGene的答案,這裡只列出目錄名稱:

for f in */; do
  echo "Directory -> $f"
done

4
2018-02-11 14:36





為什麼不將IFS設置為換行符,然後捕獲輸出 ls 在一個數組?將IFS設置為換行符應解決文件名中有趣字符的問題;運用 ls 可以很好,因為它有一個內置的排序工具。

(在測試中,我無法將IFS設置為 \n 但是將其設置為換行退格鍵,如此處其他地方所建議的那樣:

例如。 (假設需要 ls 傳入的搜索模式 $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

這在OS X上尤其方便,例如,捕獲按創建日期(從最舊到最新)排序的文件列表 ls 命令是 ls -t -U -r


1
2018-05-10 22:36



文件名也可以包含換行符,它們通常在允許用戶命名自己的文件時執行。打破 \n 不夠。唯一有效的解決方案使用帶有路徑名擴展的for循環,或 find。 - ghoti
傳輸文件名列表的唯一可靠方法是將它們與NUL字符分開,因為這是唯一一個絕對不包含在文件路徑中的字符。 - glglgl


我就是這樣做的,但可能有更有效的方法。

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt

-3
2017-08-28 17:28



堅持用管道代替文件:>ls | while read i; do echo filename: $i; done - Jeremy L
涼。我應該說你也可以在兩個命令之間使用$ EDITOR filelist.txt。你可以在編輯器中做的很多事情比在命令行上更容易。但是,與這個問題無關。 - TREE
您的解決方案根本不能解決包含換行符和其他奇特內容的文件名的問題。 - glglgl