Bagaimana saya menggunakan find ketika nama file mengandung spasi?

17

Saya ingin mem-pipe nama file ke program lain, tetapi semuanya tersedak ketika namanya mengandung spasi.

Katakanlah saya memiliki file yang dipanggil.

foo bar

Bagaimana saya bisa findmengembalikan nama yang benar?

Jelas saya ingin:

foo\ bar

atau:

"foo bar"

EDIT : Saya tidak ingin melanjutkan xargs, saya ingin mendapatkan string yang diformat dengan benar findsehingga saya dapat menyalurkan string nama file secara langsung ke program lain.

bug
sumber
5
apa tujuanmu? apakah Anda mengetahui adanya -execbendera tersebut find? Anda berpotensi mengurangi kesalahan ini dan membuat perintah Anda lebih efisien dengan melakukan -execalih - alih mengirimnya ke perintah lain. Just my $ .02
h3rrmiller
6
@bug: findmemformat nama file dengan baik; mereka dituliskan satu nama per baris. (Tentu saja, ini ambigu jika nama file mengandung karakter baris baru.) Jadi masalahnya adalah penerima menerima "tersedak" ketika mendapat spasi, yang berarti Anda harus memberi tahu kami apa yang menjadi penerima adalah jika Anda ingin jawaban yang bermakna .
rici
2
Apa yang Anda sebut "diformat dengan benar" benar-benar "lolos untuk dikonsumsi oleh shell". Sebagian besar utilitas yang dapat membaca banyak nama file akan mencekik nama yang lolos dari shell, tetapi sebenarnya masuk akal jika (misalnya) findmenawarkan opsi untuk menampilkan nama file dalam format yang sesuai untuk shell. Namun, secara umum, ekstensi -print0GNU findberfungsi dengan baik untuk banyak skenario lain (juga), dan Anda harus belajar menggunakannya dalam peristiwa apa pun.
tripleee
2
@bug: Omong-omong, ls $(command...)tidak memberi makan daftar melalui stdin. Ini menempatkan output $(command...)langsung ke baris perintah. Dalam hal ini, itu adalah shell yang membaca dari c, dan itu akan menggunakan nilai saat ini $IFSuntuk memutuskan bagaimana kata-kata mencantumkan output. Secara umum, Anda lebih baik menggunakan xargs. Anda tidak akan melihat hit kinerja.
rici
2
find -printf '"%p"\n'akan menambahkan tanda kutip ganda di sekitar setiap nama yang ditemukan, tetapi tidak akan mengutip dengan benar tanda kutip ganda dalam nama file. Jika nama file Anda tidak memiliki tanda kutip ganda yang disematkan, Anda dapat mengabaikan masalah: atau menyalurkannya sed 's/"/&&/g;s/^""/"/;s/""$/"/'. Jika nama file Anda akhirnya ditangani oleh shell, Anda mungkin harus menggunakan tanda kutip tunggal, bukan tanda kutip ganda, (jika tidak sweet$HOMEakan menjadi sesuatu seperti sheet/home/you). Dan ini masih sangat tidak kuat terhadap nama file dengan baris baru di dalamnya. Bagaimana Anda ingin mengatasinya?
tripleee

Jawaban:

18

POSIXLY:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

Dengan finddukungan -print0dan xargsdukungan -0:

find . -type f -print0 | xargs -0 <command>

-0 Opsi memberi tahu xargs untuk menggunakan karakter ASCII NUL alih-alih ruang untuk mengakhiri (memisahkan) nama file.

Contoh:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
cuonglm
sumber
Tidak bekerja Ketika saya menjalankan ls $(find . -maxdepth 1 -type f -print0 | xargs -0)saya mendapatkan ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
bug
1
Sudahkah Anda mencobanya seperti yang ditulis Gnouc? Jika Anda bersikeras melakukannya dengan cara Anda, coba lampirkan $(..)tanda kutip ganda"$(..)"
evilsoup
3
@bug: perintah Anda salah. Coba tepatnya aku ingin dan membaca halaman manual finddan xargs.
cuonglm
Begitu ya, lagi-lagi saya ingin mendapatkan string yang diformat yang bisa saya pipirkan langsung.
bug
1
@bug: Cukup gunakan xargs -0 <program Anda>
cuonglm
10

Menggunakan -print0adalah salah satu pilihan, tetapi tidak semua program mendukung menggunakan data NullByte dipisahkan sungai, sehingga Anda akan harus menggunakan xargsdengan -0pilihan untuk beberapa hal, sebagai jawaban Gnouc mencatat.

Sebuah alternatif akan digunakan finds' -execatau -execdiropsi. Yang pertama dari yang berikut ini akan memberi nama file somecommandsatu per satu, sedangkan yang kedua akan berkembang ke daftar file:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

Anda mungkin menemukan bahwa Anda lebih baik menggunakan globbing dalam banyak kasus. Jika Anda memiliki shell modern (bash 4+, zsh, ksh), Anda bisa mendapatkan globbing rekursif dengan globstar( **). Dalam bash, Anda harus mengatur ini:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

Saya memiliki garis yang mengatakan shopt -s globstar extglobdi .bashrc saya, jadi ini selalu diaktifkan untuk saya (dan juga gumpalan diperpanjang, yang juga berguna).

Jika Anda tidak ingin rekursif, jelas gunakan ./*.txtsaja, untuk menggunakan setiap * .txt di direktori kerja. findmemiliki beberapa kemampuan pencarian berbutir halus yang sangat berguna, dan wajib untuk puluhan ribu file (saat itu Anda akan menemukan argumen dalam jumlah maksimum shell), tetapi untuk penggunaan sehari-hari seringkali tidak diperlukan.

Evilsoup
sumber
Hai @ evilsoup, apa yang dilakukan {} dalam skrip ini?
Ayusman
3

Secara pribadi, saya akan menggunakan -exectindakan temukan untuk memecahkan masalah semacam ini. Atau, jika perlu xargs,, yang memungkinkan eksekusi paralel.

Namun, ada cara finduntuk menghasilkan daftar nama file yang bisa dibaca oleh bash. Tidak mengherankan, ia menggunakan -execdan bash, khususnya ekstensi untuk printfperintah:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

Namun, sementara itu akan mencetak dengan benar kata-kata yang keluar dengan shell, itu tidak akan dapat digunakan dengan $(...), karena $(...)tidak menafsirkan kutipan atau lolos. (Hasil dari $(...)tunduk pada pemisahan kata dan perluasan pathname, kecuali dikelilingi oleh tanda kutip.) Jadi yang berikut ini tidak akan melakukan apa yang Anda inginkan:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

Yang harus Anda lakukan adalah:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(Perhatikan bahwa saya tidak melakukan upaya nyata untuk menguji keburukan di atas.)

Namun, Anda sebaiknya melakukannya:

find ... -exec ls {} +
Rici
sumber
Saya tidak berpikir lsskenario cukup menangkap kasus penggunaan OP, tapi ini hanya spekulasi, karena kita belum ditunjukkan apa yang sebenarnya dia coba capai. Solusi ini sebenarnya bekerja dengan sangat baik; Saya mendapatkan output yang saya (samar-samar) diharapkan untuk semua nama file lucu yang saya coba, termasuktouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
tripleee
@triplee: Saya juga tidak tahu apa yang OP ingin lakukan. Satu-satunya keuntungan nyata dari membangun string yang dikutip untuk dilewati evaladalah Anda belum harus meneruskannya eval; Anda bisa menyimpannya di parameter dan menggunakannya nanti, mungkin beberapa kali dengan perintah yang berbeda. Namun, OP tidak memberikan indikasi bahwa itu adalah use case (dan jika ya, mungkin lebih baik untuk memasukkan nama file ke dalam array, meskipun itu juga sulit.)
rici
0
find ./  | grep " "

akan memberi Anda file dan direktori berisi spasi

find ./ -type f  | grep " " 

akan memberi Anda file berisi spasi

find ./ -type d | grep " "

akan memberi Anda direktori berisi spasi

Kannan Kumarasamy
sumber
-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'
pengguna283965
sumber
Ini adalah respons yang menarik, tetapi itu bukan jawaban untuk pertanyaan ini.
Scott