両方のディレクトリにあるすべての同じファイルの出力を比較するために、次のスクリプトを作成しました。
#!/bin/bash
for file in `find . -name "*.csv"`
do
echo "file = $file";
diff $file /some/other/path/$file;
read char;
done
私はこれを達成する別の方法があることを知っています。しかし、奇妙なことに、ファイルにスペースが含まれている場合、スクリプトは失敗します。この問題にどのように対処する必要がありますか?
findの出力例:
./zQuery - abc - Do Not Prompt for Date.csv
ベストアンサー1
単答型(回答に最も近いが空白処理)
OIFS="$IFS"
IFS=$'\n'
for file in `find . -type f -name "*.csv"`
do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read line
done
IFS="$OIFS"
より良い回答(ファイル名のワイルドカードおよび改行文字も処理)
find . -type f -name "*.csv" -print0 | while IFS= read -r -d '' file; do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
done
最高の答え(基準:ザイルズの答え)
find . -type f -name '*.csv' -exec sh -c '
file="$0"
echo "$file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
' exec-sh {} ';'
sh
または、ファイルごとに1つずつ実行しないことをお勧めします。
find . -type f -name '*.csv' -exec sh -c '
for file do
echo "$file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
done
' exec-sh {} +
長い答え
3つの質問があります。
- デフォルトでは、シェルはコマンドの出力をスペース、タブ、および改行に分割します。
- ファイル名には、拡張されるワイルドカード文字を含めることができます。
- 名前で終わるディレクトリがある場合はどうなりますか
*.csv
?
1.改行文字のみに分割
何を設定するかを調べるために、file
シェルは出力を取得してfind
何らかの方法で解釈する必要があります。それ以外の場合はfile
フル出力になりますfind
。
シェルはIFS
デフォルトで設定された変数を読み込みます。<space><tab><newline>
次に、出力の各文字を調べますfind
。で文字が見つかるとすぐにIFS
ファイル名の終わりが表示されていると思って、file
これまで見た文字に設定してループを実行します。次に、最後に停止した場所から次のファイル名を取得し、出力の終わりに達するまで次のループを実行します。
したがって、効果的に次のことを行います。
for file in "zquery" "-" "abc" ...
改行でのみ入力を分割するように指示するには、次の手順を実行する必要があります。
IFS=$'\n'
あなたの命令の前にfor ... find
。
IFS
これは単一の改行に設定されるため、スペースとタブではなく改行にのみ分割されます。
または代わりにorsh
を使用する場合は、次のように書く必要があります。dash
ksh93
bash
zsh
IFS=$'\n'
IFS='
'
これはスクリプトを操作するのに十分かもしれませんが、他の特別なケースを適切に処理することに興味がある場合は、読んでください。
2.$file
ワイルドカード拡張を使用しないでください。
ループ内部
diff $file /some/other/path/$file
シェルは$file
(再び!)拡張を試みます。
スペースを含めることができますが、上記IFS
で設定したので、ここでは問題ありません。
ただし、予測不能な動作を引き起こす可能性がある、*
または同じワイルドカード文字が含まれる可能性があります。?
(この点を指摘してくれたGilesに感謝します。)
ワイルドカードを拡張しないようにシェルに指示するには、変数を二重引用符で囲みます。
diff "$file" "/some/other/path/$file"
同じ問題が私たちを悩ませる可能性があります
for file in `find . -name "*.csv"`
たとえば、次の3つのファイルがある場合
file1.csv
file2.csv
*.csv
(可能性はほとんどありませんが、まだ可能です)
まるで逃げたような
for file in file1.csv file2.csv *.csv
これは次のように拡張されます。
for file in file1.csv file2.csv *.csv file1.csv file2.csv
2回発生しfile1.csv
、file2.csv
処理されました。
代わりに、私たちはしなければなりません
find . -name "*.csv" -print | while IFS= read -r file; do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
done
read
標準入力から行を読み、行を単語に分割し、IFS
指定した変数名に保存します。
ここでは、行を単語に分割せずに保存しないように指示します$file
。
read line
に変更されたことをお知らせしますread line </dev/tty
。
これは、ループ内の標準入力がfind
パイプから出るためです。
これにより、read
ファイル名の一部または全部が消費され、一部のファイルはスキップされます。
/dev/tty
ユーザーがスクリプトを実行する端末です。 cronを介してスクリプトを実行するとエラーが発生しますが、この場合は問題にならないと思います。
それでは、ファイル名に改行文字が含まれている場合はどうなりますか?
パイプラインの末尾から次に-print
変更-print0
して使用してそれを処理できます。read -d ''
find . -name "*.csv" -print0 | while IFS= read -r -d '' file; do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read char </dev/tty
done
これにより、find
各ファイル名の末尾にヌルバイトが追加されます。ヌルバイトはファイル名には許可されない唯一の文字なので、どんなに奇妙な場合でも、可能なすべてのファイル名を処理する必要があります。
相手のファイル名を取得するにはIFS= read -r -d ''
。
上記で使用されている場合は、デフォルトの行read
区切り文字の改行を使用しましたが、現在はfind
行区切り文字としてnullを使用します。では、bash
コマンド(組み込みコマンドでも)の引数としてNUL文字を渡すことはできませんが、意味としてbash
理解できます。-d ''
NULで区切られた。したがって、私たちは同じ行区切り文字を使って-d ''
makeを使います。 NULバイトはサポートされておらず、空の文字列として扱われるため、BTWも機能します。read
find
-d $'\0'
bash
-r
正確性のために、ファイル名のバックスラッシュを特に処理しないことを追加しました。たとえば、noは削除-r
され\<newline>
、\n
に変換されますn
。
ヌルバイトに対する上記のすべての規則を要求しbash
たり覚えていないより移植性のある作成方法です(Gillesにもう一度感謝します)。zsh
find . -name '*.csv' -exec sh -c '
file="$0"
echo "$file"
diff "$file" "/some/other/path/$file"
read char </dev/tty
' exec-sh {} ';'
*3.名前が続くディレクトリをスキップします。.csv
find . -name "*.csv"
名前付きディレクトリも一致しますsomething.csv
。
これを防ぐには、コマンド-type f
に追加してください。find
find . -type f -name '*.csv' -exec sh -c '
file="$0"
echo "$file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
' exec-sh {} ';'
〜のようにグレンジャックマンどちらの例でも、各ファイルに対して実行されるコマンドはサブシェルで実行されるため、ループ内の変数が変更されると忘れてしまうことに注意してください。
変数を設定し、ループの終わりに引き続き設定する必要がある場合は、次のようにプロセス置換を使用するように変数をオーバーライドできます。
i=0
while IFS= read -r -d '' file; do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
i=$((i+1))
done < <(find . -type f -name '*.csv' -print0)
echo "$i files processed"
これをコピーしてコマンドラインに貼り付けようとすると消費されるため、read line
コマンドecho "$i files processed"
は実行されません。
これを防ぐには、結果を削除してread line </dev/tty
結果をポケットベル(たとえば)に送信できますless
。
ノート
;
ループ内のセミコロン()を削除しました。必要に応じて入れ直すことができますが、必要ではありません。
今日$(command)
では`command`
。これは$(command1 $(command2))
主に`command1 \`command2\``
。
read char
文字は実際には読み取られません。行全体を読むので、に変更しましたread line
。