Bash

當路徑作為參數給出時,‘For’循環不會遍歷列表

  • December 3, 2021

我正在練習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.txtto file4.txt,則將腳本稱為

./my_script.sh ./*

實際上會被解釋為

./my_script.sh file1.txt file2.txt file3.txt file4.txt

這些將是 shell 腳本看到的參數。

如需進一步閱讀,請查看Bash 參考手冊中關於 shell 擴展順序的部分。

有兩種方法可以解決這個問題:

  1. 如果您確定始終要遍歷給定目錄中的所有文件,請將目錄作為參數傳遞,然後遍歷
for f in "$1"/*
do
   # operations on "$f"
done
  1. 或者,如果您確定您只會傳遞要操作的文件名,請遍歷整個參數列表,如
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 發行版中也可以作為獨立工具使用,以防止這種(和其他)可能的錯誤源。

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