(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$'に注意してください