(8つの回答)
3年前に閉鎖されました。
次のスクリプトを作成して、すべて同じファイルを含む2つのディレクターの出力を比較しました。
`findの#!/bin/bash
for file in `find . -name "*.csv"`
do
echo "file = $file";
diff $file /some/other/path/$file;
read char;
done
私はこれを達成する他の方法があることを知っています。不思議なことに、ファイルにスペースが含まれていると、このスクリプトは失敗します。どうすればこれに対処できますか?
検索の出力例:
./zQuery - abc - Do Not Prompt for Date.csv
承認された回答:
短い回答(回答に最も近いが、スペースを処理する)
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
ベストアンサー(Gillesの回答に基づく)
find . -type f -name '*.csv' -exec sh -c '
file="$0"
echo "$file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
' exec-sh {} ';'
またはさらに良いことに、1つの shを実行しないようにします ファイルごと:
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を読み取ります に設定されている変数 デフォルトで。
次に、 findの出力の各文字を調べます。 。 IFSにある文字が表示されるとすぐに 、それはファイル名の終わりを示していると考えているので、 fileを設定します これまでに見た文字に合わせてループを実行します。次に、中断したところから次のファイル名を取得し、出力の最後に到達するまで次のループなどを実行します。
つまり、これを効果的に行っています:
for file in "zquery" "-" "abc" ...
改行でのみ入力を分割するように指示するには、次のことを行う必要があります
IFS=$'n'
for ... findの前 コマンド。
これにより、 IFSが設定されます 単一の改行に分割されるため、スペースやタブではなく、改行でのみ分割されます。
shを使用している場合 またはdash ksh93の代わりに 、 bash またはzsh 、 IFS =$'n'と書く必要があります 代わりにこのように:
IFS='
'
スクリプトを機能させるにはおそらくこれで十分ですが、他のコーナーケースを適切に処理することに関心がある場合は、以下をお読みください…
2。 $ fileを展開しています ワイルドカードなし
あなたがするループの内側
diff $file /some/other/path/$file
シェルは$fileを展開しようとします (再び!)
スペースを含めることもできますが、すでに IFSを設定しているため 上記では、それはここでは問題になりません。
ただし、 *などのワイルドカード文字を含めることもできます。 または? 、これは予測できない動作につながります。 (これを指摘してくれたGillesに感謝します。)
ワイルドカード文字を展開しないようにシェルに指示するには、変数を二重引用符で囲みます。例:
diff "$file" "/some/other/path/$file"
同じ問題が私たちを噛む可能性もあります
`find内のファイルのfor file in `find . -name "*.csv"`
たとえば、これら3つのファイルがある場合
file1.csv
file2.csv
*.csv
(可能性は非常に低いですが、それでも可能です)
関連:tarファイルのアクセス許可を変更した場合、そのファイル内のファイルに適用されますか?走ったかのように
for file in file1.csv file2.csv *.csv
に拡張されます
for file in file1.csv file2.csv *.csv file1.csv file2.csv
file1.csvを引き起こします およびfile2.csv 2回処理されます。
代わりに、私たちはしなければなりません
find . -name "*.csv" -print | while IFS= read -r file; do
echo "file = $file"
diff "$file" "/some/other/path/$file"
read line </dev/tty
done
読むコード> 標準入力から行を読み取り、 IFSに従って行を単語に分割します 指定した変数名に保存します。
ここでは、行を単語に分割せず、行を $ fileに格納するように指示しています。 。
読み取り行にも注意してください read line に変更されました 。
これは、ループ内では、標準入力が 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が作成されます 各ファイル名の最後にnullバイトを置きます。ヌルバイトはファイル名で許可されていない唯一の文字であるため、これはどんなに奇妙なものであっても、考えられるすべてのファイル名を処理する必要があります。
反対側のファイル名を取得するには、 IFS =read -r -d''を使用します。 。
readを使用した場所 上記では、改行のデフォルトの行区切り文字を使用しましたが、現在は find 行区切り文字としてnullを使用しています。 bashで 、コマンドの引数にNUL文字を渡すことはできません(組み込みの文字であっても)が、 bash -d''を理解します NUL区切りの意味として 。したがって、 -d''を使用します readを作成するには findと同じ行区切り文字を使用します 。 -d$'に注意してください