Menggunakan loop seperti
for i in `find . -name \*.txt`
akan rusak jika beberapa nama file memiliki spasi di dalamnya.
Teknik apa yang bisa saya gunakan untuk menghindari masalah ini?
Menggunakan loop seperti
for i in `find . -name \*.txt`
akan rusak jika beberapa nama file memiliki spasi di dalamnya.
Teknik apa yang bisa saya gunakan untuk menghindari masalah ini?
Jawaban:
Idealnya Anda tidak melakukannya dengan cara sama sekali, karena mengurai nama file dengan benar dalam skrip shell selalu sulit (perbaiki untuk spasi, Anda masih akan mengalami masalah dengan karakter yang disematkan lainnya, khususnya baris baru). Ini bahkan terdaftar sebagai entri pertama di halaman BashPitfalls.
Yang mengatakan, ada cara untuk hampir melakukan apa yang Anda inginkan:
oIFS=$IFS
IFS=$'\n'
find . -name '*.txt' | while read -r i; do
# use "$i" with whatever you're doing
done
IFS=$oIFS
Ingatlah untuk juga mengutip $i
ketika menggunakannya, untuk menghindari hal-hal lain menafsirkan spasi nanti. Juga ingat untuk mengatur $IFS
kembali setelah menggunakannya, karena tidak melakukan hal itu akan menyebabkan kesalahan membingungkan nantinya.
Ini memang memiliki satu peringatan lain yang terlampir: apa yang terjadi di dalam while
loop dapat terjadi dalam subkulit, tergantung pada shell yang tepat Anda gunakan, sehingga pengaturan variabel mungkin tidak bertahan. Versi for
loop menghindari itu tetapi dengan harga itu, bahkan jika Anda menerapkan $IFS
solusi untuk menghindari masalah dengan spasi, Anda kemudian akan mendapat masalah jika find
pengembalian terlalu banyak file.
Pada titik tertentu perbaikan yang benar untuk semua ini menjadi melakukannya dalam bahasa seperti Perl atau Python, bukan shell.
Gunakan find -print0
dan pipa untuk xargs -0
, atau menulis program C kecil Anda sendiri dan pipa ke program C kecil Anda. Ini untuk apa -print0
dan -0
diciptakan untuk.
Script shell bukan cara terbaik untuk menangani nama file dengan spasi di dalamnya: Anda bisa melakukannya, tetapi itu menjadi kikuk.
Anda dapat mengatur "pemisah bidang internal" ( IFS
) ke sesuatu selain ruang untuk pemisahan argumen loop, misalnya
ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
IFS=${ORIGIFS}
#do stuff
done
IFS=${ORIGIFS}
Saya reset IFS
setelah penggunaannya di find, sebagian besar karena tampilannya bagus, saya pikir. Saya belum melihat ada masalah dalam mengaturnya ke baris baru, tapi saya pikir ini "bersih".
Metode lain, tergantung pada apa yang ingin Anda lakukan dengan output dari find
, adalah menggunakan langsung -exec
dengan find
perintah, atau menggunakan -print0
dan menyalurkannya ke xargs -0
. Dalam kasus pertama find
menangani nama file yang melarikan diri. Dalam hal -print0
ini, find
cetak hasilnya dengan pemisah nol, dan kemudian xargs
bagi ini. Karena tidak ada nama file yang dapat mengandung karakter itu (apa yang saya ketahui), ini selalu aman juga. Ini sebagian besar berguna dalam kasus-kasus sederhana; dan biasanya bukan pengganti yang bagus untuk for
loop penuh .
find -print0
denganxargs -0
Menggunakan find -print0
dikombinasikan dengan xargs -0
benar-benar kuat terhadap nama file hukum, dan merupakan salah satu metode yang paling dapat dikembangkan Misalnya, Anda menginginkan daftar setiap file PDF dalam direktori saat ini. Anda bisa menulis
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo
Ini akan menemukan setiap PDF (via -iname '*.pdf'
) di direktori saat ini ( .
) dan setiap sub-direktori, dan meneruskannya sebagai argumen ke echo
perintah. Karena kami menentukan -n 1
opsi, xargs
hanya akan melewati satu argumen pada satu waktu untuk echo
. Seandainya kita menghilangkan opsi itu, xargs
akan melewati sebanyak mungkin echo
. (Anda dapat echo short input | xargs --show-limits
melihat berapa byte yang diizinkan dalam baris perintah.)
xargs
dilakukan, tepatnya?Kita dapat dengan jelas melihat efek xargs
pada inputnya - dan efek -n
khususnya - dengan menggunakan skrip yang menggemakan argumennya dengan cara yang lebih tepat daripada echo
.
$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"
[[ $# -eq 0 ]] && exit
for i in $(seq 1 $#); do
echo "Arg $i: <$1>"
shift
done
EOF
$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh
Perhatikan bahwa ia menangani spasi dan baris baru dengan sangat baik,
$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh
yang akan sangat menyusahkan dengan solusi umum berikut:
chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
./echoArgs.sh "$file"
done
Catatan
Saya tidak setuju dengan bash
bashers, karena bash
, bersama dengan set alat * nix, cukup mahir dalam menangani file (termasuk yang namanya memiliki spasi putih).
Sebenarnya, find
memberi Anda kendali butir yang baik untuk memilih file mana yang akan diproses ... Di sisi bash, Anda benar-benar hanya perlu menyadari bahwa Anda harus membuat Anda merangkai bash words
; biasanya dengan menggunakan "tanda kutip ganda", atau mekanisme lain seperti menggunakan IFS, atau temukan{}
Perhatikan bahwa dalam sebagian besar / banyak situasi Anda tidak perlu mengatur dan mengatur ulang IFS; cukup gunakan IFS secara lokal seperti ditunjukkan dalam contoh di bawah ini. Ketiganya menangani ruang putih dengan baik. Anda juga tidak memerlukan struktur loop "standar", karena find \;
secara efektif adalah loop; cukup masukkan logika loop Anda ke fungsi bash (jika Anda tidak memanggil alat standar).
IFS=$'\n' find ~/ -name '*.txt' -exec function-or-util {} \;
Dan, dua contoh lagi
IFS=$'\n' find ~/ -name '*.txt' -exec printf 'Hello %s\n' {} \;
IFS=$'\n' find ~/ -name '*.txt' -exec echo {} \+ |sed 's/home//'
'temukan also allows you to pass multiple filenames as args to you script ..(if it suits your need: use
+ instead
\; `)
find -print0
danxargs -0
.