(8 jawaban)
Tutup 3 tahun yang lalu.
Saya menulis skrip berikut untuk membedakan output dari dua direktori dengan semua file yang sama di dalamnya:
#!/bin/bash
for file in `find . -name "*.csv"`
do
echo "file = $file";
diff $file /some/other/path/$file;
read char;
done
Saya tahu ada cara lain untuk mencapai ini. Anehnya, skrip ini gagal ketika file memiliki spasi di dalamnya. Bagaimana saya bisa mengatasi ini?
Contoh keluaran find:
./zQuery - abc - Do Not Prompt for Date.csv
Jawaban yang Diterima:
Jawaban singkat (paling dekat dengan jawaban Anda, tetapi menggunakan spasi)
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"
Jawaban yang lebih baik (juga menangani karakter pengganti dan baris baru dalam nama file)
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
Jawaban terbaik (berdasarkan jawaban 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 {} ';'
Atau bahkan lebih baik, untuk menghindari menjalankan satu sh
per berkas:
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 {} +
Jawaban panjang
Anda memiliki tiga masalah:
- Secara default, shell membagi output perintah pada spasi, tab, dan baris baru
- Nama file dapat berisi karakter wildcard yang akan diperluas
- Bagaimana jika ada direktori yang namanya berakhiran
*.csv
?
1. Memisahkan hanya pada baris baru
Untuk mengetahui apa yang harus disetel file
untuk, shell harus mengambil output dari find
dan menafsirkannya entah bagaimana, jika tidak file
hanya akan menjadi seluruh output dari find
.
Shell membaca IFS
variabel, yang disetel ke <space><tab><newline>
secara default.
Kemudian terlihat pada setiap karakter pada output find
. Segera setelah melihat karakter apa pun yang ada di IFS
, ia berpikir bahwa menandai akhir dari nama file, sehingga menetapkan file
ke karakter apa pun yang dilihatnya sampai sekarang dan menjalankan loop. Kemudian dimulai dari tempat terakhirnya untuk mendapatkan nama file berikutnya, dan menjalankan loop berikutnya, dll., hingga mencapai akhir keluaran.
Jadi ini efektif melakukan ini:
for file in "zquery" "-" "abc" ...
Untuk memberitahunya agar hanya membagi input pada baris baru, Anda perlu melakukan
IFS=$'n'
sebelum for ... find
perintah.
Itu menetapkan IFS
ke satu baris baru, sehingga hanya terbagi pada baris baru, bukan spasi dan tab juga.
Jika Anda menggunakan sh
atau dash
bukannya ksh93
, bash
atau zsh
, Anda perlu menulis IFS=$'n'
seperti ini sebagai gantinya:
IFS='
'
Itu mungkin cukup untuk membuat skrip Anda berfungsi, tetapi jika Anda tertarik untuk menangani beberapa kasus sudut lainnya dengan benar, baca terus…
2. Memperluas $file
tanpa karakter pengganti
Di dalam loop tempat Anda melakukannya
diff $file /some/other/path/$file
shell mencoba untuk memperluas $file
(lagi!).
Itu bisa berisi spasi, tapi karena kita sudah mengatur IFS
di atas, itu tidak akan menjadi masalah di sini.
Tapi itu juga bisa berisi karakter wildcard seperti *
atau ?
, yang akan mengarah pada perilaku yang tidak terduga. (Terima kasih kepada Gilles karena telah menunjukkan hal ini.)
Untuk memberi tahu shell agar tidak memperluas karakter wildcard, letakkan variabel di dalam tanda kutip ganda, mis.
diff "$file" "/some/other/path/$file"
Masalah yang sama juga bisa menggigit kita di
for file in `find . -name "*.csv"`
Misalnya, jika Anda memiliki tiga file ini
file1.csv
file2.csv
*.csv
(sangat tidak mungkin, tapi masih mungkin)
Terkait:Jika saya mengubah izin pada file tar, apakah itu berlaku untuk file di dalamnya?Seolah-olah Anda telah berlari
for file in file1.csv file2.csv *.csv
yang akan diperluas ke
for file in file1.csv file2.csv *.csv file1.csv file2.csv
menyebabkan file1.csv
dan file2.csv
untuk diproses dua kali.
Sebaliknya, kita harus melakukan
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
membaca baris dari input standar, membagi baris menjadi kata-kata sesuai dengan IFS
dan menyimpannya dalam nama variabel yang Anda tentukan.
Di sini, kami memintanya untuk tidak membagi baris menjadi kata-kata, dan menyimpan baris di $file
.
Perhatikan juga bahwa read line
telah berubah menjadi read line </dev/tty
.
Ini karena di dalam loop, input standar berasal dari find
melalui jalur pipa.
Jika kita baru saja read
, itu akan memakan sebagian atau seluruh nama file, dan beberapa file akan dilewati.
/dev/tty
adalah terminal tempat pengguna menjalankan skrip. Perhatikan bahwa ini akan menyebabkan kesalahan jika skrip dijalankan melalui cron, tetapi saya menganggap ini tidak penting dalam kasus ini.
Lalu, bagaimana jika nama file berisi baris baru?
Kita bisa mengatasinya dengan mengubah -print
ke -print0
dan menggunakan read -d ''
di ujung saluran:
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
Ini membuat find
letakkan byte nol di akhir setiap nama file. Byte nol adalah satu-satunya karakter yang tidak diperbolehkan dalam nama file, jadi ini harus menangani semua kemungkinan nama file, tidak peduli seberapa anehnya.
Untuk mendapatkan nama file di sisi lain, kami menggunakan IFS= read -r -d ''
.
Di mana kami menggunakan read
di atas, kami menggunakan pembatas baris default dari baris baru, tetapi sekarang, find
menggunakan null sebagai pembatas baris. Di bash
, Anda tidak dapat meneruskan karakter NUL dalam argumen ke perintah (bahkan yang bawaan), tetapi bash
mengerti -d ''
sebagai artinya dibatasi NUL . Jadi kami menggunakan -d ''
untuk membuat read
gunakan pembatas baris yang sama dengan find
. Perhatikan bahwa -d $'