bagaimana cara melewatkan hasil `find` sebagai daftar file?

11

Situasinya adalah, saya memiliki pemutar MP3 mpg321yang menerima daftar file sebagai argumen. Saya menyimpan musik saya di direktori bernama "musik", di mana ada beberapa direktori lagi. Saya hanya ingin memainkan semuanya, jadi saya menjalankan program dengan

mpg321 $(find /music -iname "*\.mp3")

. Masalahnya adalah, beberapa nama file memiliki spasi di dalamnya, dan program memecah nama-nama itu menjadi bagian-bagian yang lebih kecil dan mengeluh tentang file yang hilang. Membungkus hasil finddalam tanda kutip

mpg321 "$(find /music -iname "*\.mp3")"

tidak membantu karena semua akan menjadi satu "nama file" besar, yang jelas tidak ditemukan.

Bagaimana saya bisa melakukan ini? Jika itu penting, saya menggunakan bash, tetapi akan segera beralih zsh.

phhehehe
sumber

Jawaban:

17

Coba gunakan temukan -print0atau -printfopsi dalam kombinasi dengan xargsseperti ini:

find /music -iname "*\.mp3" -print0 | xargs -0 mpg321

Cara kerjanya dijelaskan oleh halaman buku panduan find :

-print0

Benar; cetak nama file lengkap pada output standar, diikuti dengan karakter nol (bukan karakter baris baru yang menggunakan -print). Ini memungkinkan nama file yang berisi baris baru atau jenis ruang putih lainnya diinterpretasikan dengan benar oleh program yang memproses hasil pencarian. Opsi ini sesuai dengan opsi -0 dari xargs.

Steven D
sumber
Maaf untuk semua pengeditan. Saya ingat pola-print0 xarg -0 gagal pada tipe spasi lain, tapi saya tidak bisa menemukan contoh.
Steven D
oh ya itu berhasil! tapi saya masih bertanya-tanya mengapa mpg321 $(find /music -iname "*\.mp3" -print0)tidak?
phunehehe
1
Yah, saya percaya itu tidak berhasil karena dua alasan: (1) nama file dipisahkan oleh karakter nol, yang bukan apa yang diharapkan oleh mpg321 sama sekali dan (2) xargs sedang melakukan pekerjaan melarikan diri dari spasi lain, tidak Temukan.
Steven D
2
@ phunehehe, @Steven: mpg321tidak ada hubungannya dengan itu, itu shell yang memecah output findmenjadi argumen yang terpisah. Dan -print0 | xargs -0akan bekerja dengan setiap kemungkinan nama file.
Gilles 'SO- stop being evil'
8
find /music -iname "*\.mp3" -exec mpg123 {} +

Dengan GNU find, Anda juga dapat menggunakan -print0danxargs -0 , tetapi tidak ada gunanya mempelajari alat lain. The -exec ... {} +sintaks mendapat sedikit menyebutkan karena Linux memperolehnya lambat -print0, tapi tidak ada alasan untuk tidak menggunakannya sekarang.

Dengan zsh atau bash 4, ini jauh lebih sederhana:

mpg123 **/*.[Mm][Pp]3

Hanya di zsh, Anda dapat membuat (bagian dari) pola tidak sensitif-huruf:

mpg123 (#i)**/*.mp3
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Memberi +1 pada ini, "find -print0" adalah hack yang jelek.
p-static
2

Saya pikir solusi Steven adalah yang terbaik, tetapi cara lain adalah dengan menggunakan -Iflag xargs , yang memungkinkan Anda menentukan string yang kemudian akan diganti dalam perintah dengan argumen (bukan hanya menambahkan argumen ke akhir perintah). Anda dapat menggunakannya untuk mengutip argumen:

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"
Michael Mrozek
sumber
Saya tidak mengerti jawaban ini ... Anda menggunakan -0flag xargs tanpa menemukan -print0. Juga, -Ibendera xargs memiliki sejumlah konsekuensi. Tidak seperti sekadar polos xargsyang akan memampatkan semua baris dari stdin menjadi satu perintah, xargs -Iakan mengeksekusi mpg321berpotensi ratusan atau ribuan kali (satu kali untuk setiap file) yang tentunya bukan maksudnya. Juga perlu diingat bahwa mengutip foo tidak perlu seperti xargshalnya ini secara internal. Perhatikan jika Anda mengompres semua nama file ke baris baris dan mengirimkannya xargs -I, mereka tidak akan terbuka.
Enam
1

Biasanya yang terbaik adalah langsung -exec ${tgt_process} \{\} +tetapi jika Anda memang perlu mendapatkan daftar nama file yang dibatasi dengan andal dalam file atau streaming dari findalasan apa pun, maka Anda dapat melakukan ini:

find -exec sh -c 'printf "///%s///\n" "$@"' -- \{\} +

Apa yang Anda dapatkan dari itu adalah dua string unik . Di bagian atas setiap nama file adalah string \n///dan di bagian belakang setiap nama file adalah string ///\n. Dua string ini tidak terjadi di tempat lain di findoutput kecuali untuk posisi-posisi itu terlepas dari karakter apa pun yang terdapat pada nama file.

Selain itu penggunaan di atas adalah POSIX portabel awal dan dapat diandalkan untuk bekerja pada hampir semua sistem unix. Ini tidak benar dari penggunaan pembatas byte nol - meskipun nyaman - direkomendasikan oleh beberapa orang lain.

Tetapi, sekali lagi, ini hanya perlu jika Anda tidak dapat langsung -execAnda $tgt_processuntuk alasan apa pun, karena itu harus menjadi tujuan Anda. Untuk satu hal, metode di atas masih membutuhkan parsing. Sebagai contoh, jika Anda ingin setiap shell nama file dikutip, Anda harus terlebih dahulu memastikan setiap hard-quote dalam nama file tersebut lolos:

find ... + | sed 's|'\''|&"&"&|g;s|///|'\''|g'

Itu menghasilkan array yang benar shell-lolos nama file terlepas dari apa pun karakter penyusunnya. Sekarang Anda hanya perlu berharap bahwa aplikasi Anda di sisi penerima tidak akan memotongnya.

mikeserv
sumber
0

Cara lain untuk melakukan ini adalah keluar dari semua karakter khusus yang ada dalam nama file Anda. Misalnya:

find /music -iname "*\.mp3" | sed 's!\([] \*\$\/&[]\)!\\\1!g' | xargs mpg321

Ini pada dasarnya akan meneruskan nama file yang diloloskan dengan benar ke xargs untuk dieksekusi dan tidak akan ada masalah.

Priyabrata Patnaik
sumber
1
Ini masih memecah baris baru dalam nama file. Dan Anda mungkin juga sed 's|.|\\&|g'- itulah yang direkomendasikan POSIX.
mikeserv