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 $iketika menggunakannya, untuk menghindari hal-hal lain menafsirkan spasi nanti. Juga ingat untuk mengatur $IFSkembali 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 whileloop dapat terjadi dalam subkulit, tergantung pada shell yang tepat Anda gunakan, sehingga pengaturan variabel mungkin tidak bertahan. Versi forloop menghindari itu tetapi dengan harga itu, bahkan jika Anda menerapkan $IFSsolusi untuk menghindari masalah dengan spasi, Anda kemudian akan mendapat masalah jika findpengembalian terlalu banyak file.
Pada titik tertentu perbaikan yang benar untuk semua ini menjadi melakukannya dalam bahasa seperti Perl atau Python, bukan shell.
Gunakan find -print0dan pipa untuk xargs -0, atau menulis program C kecil Anda sendiri dan pipa ke program C kecil Anda. Ini untuk apa -print0dan -0diciptakan 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 IFSsetelah 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 -execdengan findperintah, atau menggunakan -print0dan menyalurkannya ke xargs -0. Dalam kasus pertama findmenangani nama file yang melarikan diri. Dalam hal -print0ini, findcetak hasilnya dengan pemisah nol, dan kemudian xargsbagi 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 forloop penuh .
find -print0denganxargs -0Menggunakan find -print0dikombinasikan dengan xargs -0benar-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 echoperintah. Karena kami menentukan -n 1opsi, xargshanya akan melewati satu argumen pada satu waktu untuk echo. Seandainya kita menghilangkan opsi itu, xargsakan melewati sebanyak mungkin echo. (Anda dapat echo short input | xargs --show-limitsmelihat berapa byte yang diizinkan dalam baris perintah.)
xargsdilakukan, tepatnya?Kita dapat dengan jelas melihat efek xargspada inputnya - dan efek -nkhususnya - 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 bashbashers, karena bash, bersama dengan set alat * nix, cukup mahir dalam menangani file (termasuk yang namanya memiliki spasi putih).
Sebenarnya, findmemberi 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 -print0danxargs -0.