Bash
當路徑作為參數給出時,‘For’循環不會遍歷列表
我正在練習shell 腳本,並試圖製作一個簡單的腳本,它將目錄作為參數,循環遍歷其中的每個文件並列印出它的名稱和大小。
#!/bin/bash # A practice shell script to try and display a list of file names # and their sizes using the output of ls -l and cut. declare -i index export index=0 export name="" export size=0 for file in $1 ; do index+=1 name=`basename $file` size=`ls -l $file | cut -d " " -f 5` echo "$index: $name, size: $size bytes" done
當我
./*
作為參數給出時,它會為一個文件執行此操作,僅此而已。但是,如果我編輯上面的程式碼並./*
替換$1
,它會工作並循環遍歷目前目錄中的所有文件。為什麼它不做同樣的事情,什麼時候
$1
應該相等./*
?
原因是呼叫腳本的 shell 在將通配模式傳遞給腳本
./*
*之前擴展了通配模式。*這意味著,如果您的 globbing 模式匹配例如file1.txt
tofile4.txt
,則將腳本稱為./my_script.sh ./*
實際上會被解釋為
./my_script.sh file1.txt file2.txt file3.txt file4.txt
這些將是 shell 腳本看到的參數。
如需進一步閱讀,請查看Bash 參考手冊中關於 shell 擴展順序的部分。
有兩種方法可以解決這個問題:
- 如果您確定始終要遍歷給定目錄中的所有文件,請將目錄作為參數傳遞,然後遍歷
for f in "$1"/* do # operations on "$f" done
- 或者,如果您確定您只會傳遞要操作的文件名,請遍歷整個參數列表,如
for f in "$@" do # operations on "$f" done
如果您想通過將 glob 模式傳遞給腳本來做到這一點 - 這當然是一個有趣的練習 - 這也是可能的(參見 @ilkkachu 的評論)。正如@fra-san 在評論中提到的那樣,該方法具有優勢 - 它可以為腳本的使用增加更多的靈活性,並且它繞過了對 shell 命令行參數的限制(參見“參數列表太長”;儘管 RAM 會仍然限制生成的文件名列表的長度) - 但需要您格外小心。
- 您可以通過將參數括在引號(單引號或雙引號)中或使用反斜杠轉義 glob 字元來防止 shell 擴展 glob:
./my_script.sh "./*" ./my_script.sh './*' ./my_script.sh ./\*
- 在腳本中,您將引用未引用的位置參數
$1
*,*以便它實際上由 shell 解釋(我們經常想要避免的事情)。- 由於該“解釋”不僅涉及擴展(見上文),還涉及分詞,因此您需要將輸入欄位分隔符
IFS
設置為空字元串,以確保不會發生分詞。- 然後
for
循環看起來像IFS= for f in $1 do # Operations on "$f" done
關於腳本的一些一般說明:
- 總是引用 shell variables,特別是當它們包含文件名時,否則你的腳本會偶然發現帶有空格或其他更奇特字元的文件名 - 請記住,即使換行符也是文件名的允許字元(yuck)!
- 出於類似原因,
ls
非常不鼓勵解析 的輸出。如果要辨識特定文件的屬性,該stat
工具是更好的選擇。為了確定文件的大小,例如,您可以使用size=$(stat --printf="%s" "$f")
- 建議使用“新”樣式進行
$( ... )
命令替換,而不是舊的“反引號樣式”...
。- 檢查 shell 腳本是一個好習慣,
shellcheck
在許多 Linux 發行版中也可以作為獨立工具使用,以防止這種(和其他)可能的錯誤源。