
bash
指定されたディレクトリ ツリー内のファイルの内容を検索して、指定された部分文字列が存在するかどうかを確認するスクリプトを作成しようとしています。
の再帰関数だけを使用するのgrep
は不十分です。/
システムのディレクトリ (およびすべてのサブディレクトリ) を反復処理する必要がある可能性があり、メモリ不足で中断する可能性があります。そのため、スクリプトに渡される引数を示す次の変数をgrep
使用して、指定されたディレクトリ ツリー内のすべてのディレクトリとサブディレクトリのリストを取得することにしました。find
searchdir=$HOME # passed in a script argument
searchstr="secret" # passed in a script argument
ユーティリティを呼び出してfind
、出力を一時ファイルに保存します。
TF=$(mktemp)
find ${searchdir} -type d 1>$TF 2>/dev/null
while-do
一時ファイル内のすべてのディレクトリのリストを使用して、各ディレクトリ内のすべてのファイルを検索する目的で、ループを使用してこのファイルの行を反復処理します。 についてはgrep
、で提供されるパラメータの形式を使用します。この答え隠しファイルも含め、単一のディレクトリ内のすべてのファイルを検索します。
cat $TF | while read line || [[ -n $line ]];
do
grepdir="${line}/{*,.*}"
grep -sHn "${searchstr}" ${grepdir}
done
...ただし、そのコードは出力を生成しません。
確認したところ…
には${TF}
すべてのディレクトリの正しいリストが含まれています。${grepdir}
変数を出力すると、期待どおりの出力が得られます。
/home/user/{*,.*}
/home/user/.ssh/{*,.*}
/home/user/test/{*,.*}
# ... and so on
grep
ハードコードされたディレクトリ、特に、~/test/
検索対象となる文字列を含む2つのテストファイルを含むディレクトリでコマンドを実行すると、
grep -sHn "${searchstr}" /home/user/test/{*,.*}
... 部分文字列「secret」を含む 2 つのファイルを正しく出力します。
/home/user/test/asdf:7:secret
/home/user/test/test.txt:5:asdfasfdsecretaasdfafd
私にとってうまくいくフォーマットは、再帰的な使用法について議論する回答grep
. こうすると:
cat $TF | while read line || [[ -n $line ]];
do
grep -rn "${line}" -e "${searchstr}"
done
... 出力はいくつか得られますが (技術的には正しいですが、重複するエントリが多数あります)、grep
ディレクトリを再帰的に処理しており、すべてのディレクトリのリストを持っているため、同じ結果が何度も得られ、前述のルート ディレクトリなどのディレクトリではgrep
完全に失敗します。これを回避しようとしています。
$(echo "${grepdir}")
また、パラメータとして渡すなど、これを動作させるために必死にハックしたが、やはり結果は出なかったことも言及しておくべきでしょう。
おそらく、 についての私の考え方や理解には誤解があるのでしょう。を呼び出す前に変数を展開すbash
べきではないのでしょうか? 私のスクリプトのどこが間違っているのでしょうか?bash
${grepdir}
grep
答え1
ルール1: コマンドやスクリプトが期待通りに動作しない場合は、
エラーメッセージを確認してください。 に投げ込まないでください /dev/null
。
次のようなエラーメッセージが表示されます
grep: /home/user/{*,.*}: No such file or directory
grep: /home/user/.ssh/{*,.*}: No such file or directory
grep: /home/user/test/{*,.*}: No such file or directory
しかし、あなたはそれらを見ていません。
見てみるとバッシュ(1)、 私たちは見る
展開は、コマンド ラインが単語に分割された後に実行されます。実行される展開には、中括弧展開、チルダ展開、パラメーターと変数の展開、コマンド置換、算術展開、単語分割、パス名展開の 7 種類があります。
展開の順序は、中括弧展開、チルダ展開、パラメータと変数の展開、算術展開、コマンド置換(左から右に実行)、単語分割、パス名展開です。
あなたの状況で重要なのは、括弧の展開が変数の展開の前に行われることです。つまり、
grep -sHn "${searchstr}" "${line}"/{*,.*}
それから
- 括弧展開により、最後のトークンが および に変換され
"${line}"/*
ます"${line}"/.*
。 /home/user/*
変数展開により、上記はとなり/home/user/.*
、その後- パス名を展開すると、上記はファイル名のリストに変換されます。
しかし、あなたが言うには
grep -sHn "${searchstr}" ${grepdir}
それから
- 変数展開により最後のトークンが になります
/home/user/{*,.*}
。
そして、括弧の展開が発生するには遅すぎます。
grep
文字通り と呼ばれるファイルを探します/home/user/{*,.*}
。
追伸
grep -sHn "${searchstr}" "${line}/{*,.*}"
引用符によって中括弧の展開とパス名の展開が妨げられるため、これも機能しません。
PPS これらすべてのブレースは必要ありません。
grep -sHn "$searchstr" "$line"/{*,.*}
大丈夫でしょう。
答え2
システム全体を再帰的に処理するときに grep が中止する理由は、データ量に対応できなかったからではなく、/proc、/sys、または /dev 内のいずれかの疑似ファイルまたはデバイス ファイルに遭遇したためである可能性があります。--exclude
コマンド ラインのオプションを使用して、問題のあるディレクトリを除外できます。
ワイルドカードが展開されない理由は、ワイルドカードが次の行で引用されているからです。
grepdir="${line}/{*,.*}"
これをこのように変更すると、拡張が容易になる可能性があります。
grepdir="${line}/"{*,.*}
find
これを実現する別の方法 (スクリプトを少なくする) は、ファイル パスを使用してファイルを選択し、処理のためにパイプすることですxargs
。find / ... -print 0 | xargs -0 ...
ただし、どちらの方法でも、除外しない限り、元の再帰 grep が遭遇したファイルは遭遇する可能性があります。