Bagaimana cara mengurai output dari perintah find ketika nama file memiliki spasi di dalamnya?

12

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?

Scott C Wilson
sumber
1
Perhatikan bahwa file juga dapat memiliki baris baru dalam nama file mereka. Itu sebabnya ada find -print0dan xargs -0.
Daniel Beck

Jawaban:

12

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.

geekosaurus
sumber
1
Saya suka gagasan hanya menggunakan Python untuk menghindari semua ini.
Scott C Wilson
12

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.

DW
sumber
Bekerja pada mesin saya ^ TM!
mcandre
2

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 .

Daniel Andersson
sumber
1

Menggunakan find -print0denganxargs -0

Menggunakan 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.)

Apa yang 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
jpaugh
sumber
1

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\; `)

Peter.O
sumber
1
Ada beberapa validitas untuk kedua perspektif. Ketika saya hanya mengerjakan file saya sendiri, saya hanya akan menggunakan find dan tidak perlu khawatir, karena file saya tidak memiliki spasi (atau carriage return!) Dalam nama mereka. Tetapi ketika Anda mulai bekerja dengan file orang lain, Anda harus menggunakan teknik yang lebih kuat.
Scott C Wilson