ディレクトリでforループが実行されないのはなぜですか

ディレクトリでforループが実行されないのはなぜですか

次のスクリプトでは、最初ののためにループは期待どおりに実行されますが、2 番目は実行されません。エラーは表示されませんが、スクリプトがハングしているだけのようです。

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」と「/for」のタグが付いているので、Bash シェルを使用していると想定します。

私の答えでは、この素晴らしいBash ガイドは、おそらく Bash を学ぶのに最適な情報源です。

1)決して使用しないでくださいコマンド置換、 のどちらか種類は引用符なしです。ここで大きな問題があります。引用符なしの展開を使用して出力を引数に分割します。

具体的には、これ$(find $DIRWORK -type d -name work)$(find $DIR -type f)単語の分割したがって、find名前にスペースが含まれるファイル (つまり「ファイル名」) が見つかった場合、Bash の単語分割結果によって、コマンドforが反復処理する 2 つの引数 (つまり「ファイル」用と「名前」用) が渡されます。この場合、実際に存在する場合に損害を与える可能性を回避するために、「ファイル: そのようなファイルまたはディレクトリはありません」および「名前: そのようなファイルまたはディレクトリはありません」という結果が返されることを期待します。

2)慣例により、環境変数 (PATH、EDITOR、SHELL など) と内部シェル変数 (BASH_VERSION、RANDOM など) はすべて大文字で表記されます。その他の変数名はすべて小文字で表記されます。変数名は大文字と小文字が区別されるため、この慣例により環境変数と内部変数が誤って上書きされることが回避されます。

$DIRWORK ディレクトリは規則に違反しており、引用符も付いていません。したがって、$DIRWORK が引用符で囲まれていない場合、 letDIRWORK='/path/to/dir1 /path/to/dir2'find2 つの異なるディレクトリを参照します。引用符の使用は 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 で 2 つに分割すると、コードが壊れます。

以下について読んでみてくださいプロセス置換私のスクリプトを完全に理解できない場合は。

find ... | while read name; do ...; done出力の読み取りに使用する必要があると述べている以前の回答findも、正しくない可能性があります。while上記のループは、親からコピーされた変数の独自のコピーを使用して、新しいサブシェルで実行されます。このコピーは、好きなように使用できます。ループがwhile終了すると、サブシェルのコピーは破棄され、親の元の変数は変更されません。

この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。つまり、が 1 行を出力するwhileとすぐに、ループは 1 回の反復を実行しますfind。 が実行を終了するのを待つ必要はありませんfind

関連情報