為什麼 for 迴圈不會在目錄上執行

為什麼 for 迴圈不會在目錄上執行

在下面的腳本中,第一個為了循環按預期執行,但不是第二個。我沒有收到任何錯誤,似乎腳本只是掛起。

HOME=/root/mydir

DIR=$HOME/var
DIRWORK=$HOME/Local

for f in $(find $DIR -type f); do

    lsof -n $f | grep [a-z] > /dev/null

    if [ $? != 0 ]; then
    echo "hi"       
    fi
done


for i in $(find $DIRWORK -type d -name work); do
    echo "2"
done

答案1

您的腳本以危險的方式編碼。

首先,我假設您正在使用 Bash shell,因為您將其標記為“/bash”和“/for”。

在我的回答中我會引用這個偉大的重擊指南,這可能是學習 Bash 的最佳來源。

1)切勿使用命令替換, 的任何一個親切,不帶引號。這裡有一個主要問題:使用不含引號的擴充將輸出拆分為參數。

具體來說,這$(find $DIRWORK -type d -name work)$(find $DIR -type f) 經歷分詞,因此如果find發現一個檔案名稱中包含空格,即“檔案名稱”,Bash 的分詞結果將傳遞 2 個參數供命令for迭代,即一個用於“檔案”,一個用於“名稱”。在這種情況下,您希望獲得“檔案:沒有這樣的檔案或目錄”和“名稱:沒有這樣的檔案或目錄”,而不是如果它們確實存在的話可能會對它們造成損壞。

2)依照慣例,環境變數(PATH、EDITOR、SHELL...)和內部 shell 變數(BASH_VERSION、RANDOM...)都是完全大寫的。所有其他變數名稱都應小寫。由於變數名稱區分大小寫,因此此約定可以避免意外覆蓋環境變數和內部變數。

您的 $DIRWORK 目錄打破了該約定,並且它也未加引號,因此如果我們 let DIRWORK='/path/to/dir1 /path/to/dir2'find當 $DIRWORK 未加引號時,將查看兩個不同的目錄。使用引號的主題在 Bash 中非常重要,因此您應該“雙引號”每一個擴展,以及任何可能包含特殊字元的內容,例如“$var”、“$@”、“${array[@]}”、“$(command)”。 Bash 將「單引號」內的所有內容視為字面意思。了解 ' 和 " 和 ` 之間的差異。請參閱引號,論據您可能還想看看這個連結:http://wiki.bash-hackers.org/syntax/words

這是腳本的更安全版本,我建議您改用:

my_home="/root/mydir"

my_dir="$my_home/var"
dir_work="$my_home/Local"

while IFS= read -r -d '' f; do
    # I'm guessing that you also want to ignore stderr;
    # this is where the 2>&1 came from.
    if lsof -n "$f" | grep '[a-z]' > /dev/null 2>&1; then
        echo "hey, I'm safer now!"
    fi
done < <(find "$dir_work" -type f -print0)


while IFS= read -r -d '' f; do
    echo "2"
done < <(find "$dir_work" -type d -name 'work' -print0)

正如您所看到的,該IFS變數被設定為空,從而防止read修剪行中的前導和尾隨空格。此read指令使用空字串 ( -d '') 作為分隔符,讀取直到到達 \0。 find需要進行相應的修改,因此它使用-print0選項來用 \0 而不是新行來分隔其資料 - 令人驚訝且惡意的是,它可以是檔案名稱的一部分。將這樣的文件按 \n 分成兩部分將會破壞我們的程式碼。

您可能想閱讀有關流程替代如果你不完全理解我的腳本。

先前的答案指出應該find ... | while read name; do ...; done用於讀取find輸出也可能很糟糕。上面的循環while在一個新的子 shell 中執行,該子 shell 具有從父 shell 複製的變數副本。然後,該副本可用於您喜歡的任何用途。當while循環結束時,子 shell 副本被丟棄,父 shell 的原始變數沒有改變。

如果您的目標是修改此while循環內的某些變數並隨後在父級中使用它們,請考慮使用上面更安全的腳本,這將防止資料遺失。

答案2

這段程式碼

for i in $(find $DIRWORK -type d -name work); do
    echo "2"
done

將會先執行這一行

find $DIRWORK -type d -name work

等待find執行完成,然後取得輸出並將其放回for循環中

for i in the output of find; do
    echo "2"
done

只有這樣 for 迴圈才會開始執行。

因此,如果find需要很長時間才能完成for循環,則必須等待很長時間才能開始。

嘗試find在互動式提示中計時命令

$ time find $DIRWORK -type d -name work

看看需要多長時間。


另請注意:不應使用for循環來循環檔案​​名稱。使用這樣的while循環:read

find $DIRWORK -type d -name work | while read name; do
    echo "2"
done

了解更多。

獎勵:這while與 並行執行循環find。這意味著循環將在列印出一行while後立即執行一次迭代。find它不必等待find完成執行。

相關內容