Bagaimana cara melewatkan file yang ditemukan oleh find sebagai argumen?

9

Pertama-tama untuk memotong jawaban yang sepele tetapi tidak dapat diterapkan: Saya tidak dapat menggunakan trik find+ xargsvariannya (seperti finddengan -exec) karena saya perlu menggunakan beberapa ekspresi seperti itu per panggilan. Saya akan kembali ke ini di akhir.


Sekarang untuk contoh yang lebih baik mari kita pertimbangkan:

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Bagaimana saya meneruskannya sebagai argumen program?

Melakukannya saja tidak berhasil

$ ./program $(find -L some/dir -name \*.abc | sort)

gagal karena programmendapat argumen berikut:

[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc

Seperti dapat dilihat, jalan dengan ruang terpecah dan programmenganggapnya sebagai dua argumen yang berbeda.

Kutip sampai berfungsi

Tampaknya pengguna pemula seperti saya, ketika dihadapkan dengan masalah seperti itu, cenderung secara acak menambahkan tanda kutip sampai akhirnya berfungsi - hanya di sini sepertinya tidak membantu ...

"$(…)"

$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Karena tanda kutip mencegah pemisahan kata, semua file dilewatkan sebagai argumen tunggal.

Mengutip jalur individu

Pendekatan yang menjanjikan:

$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"

Kutipan di sana, tentu. Tetapi mereka tidak lagi ditafsirkan. Mereka hanyalah bagian dari string. Jadi tidak hanya mereka tidak mencegah pemisahan kata, tetapi juga mereka bertengkar!

Ubah IFS

Lalu aku mencoba bermain-main dengan IFS. Saya lebih suka finddengan -print0dan sortdengan cara -zapapun - sehingga mereka tidak akan memiliki masalah pada "jalur kabel" sendiri. Jadi mengapa tidak memaksakan pemisahan kata pada nullkarakter dan memiliki semuanya?

$ ./program $(IFS=$'\0' find -L some/dir -name \*.abc -print0 | sort -z)
[0]: ./program
[1]: some/dir/1.abcsome/dir/2.abcsome/dir/a
[2]: space.abc

Jadi masih terbagi pada ruang dan tidak terpecah pada null.

Saya mencoba untuk menempatkan IFStugas di $(…)(seperti yang ditunjukkan di atas) dan sebelumnya ./program. Juga saya mencoba sintaks lain seperti \0, \x0, \x00baik dikutip dengan 'dan "serta dengan dan tanpa $. Tak satu pun dari mereka yang tampaknya membuat perbedaan ...


Dan di sini saya kehabisan ide. Saya mencoba beberapa hal lagi tetapi semua tampaknya berjalan ke masalah yang sama seperti yang tercantum.

Apa lagi yang bisa saya lakukan? Apakah bisa dilakukan?

Tentu, saya bisa programmenerima pola dan melakukan pencarian sendiri. Tapi itu banyak pekerjaan ganda sambil memperbaikinya ke sintaksis tertentu. (Bagaimana dengan menyediakan file dengan grepcontohnya?).

Saya juga bisa membuat programmenerima file dengan daftar jalur. Kemudian saya dapat dengan mudah membuang findekspresi ke beberapa file temp dan menyediakan path ke file itu saja. Ini dapat didukung berada di sepanjang jalur langsung sehingga jika pengguna hanya memiliki jalur sederhana itu dapat disediakan tanpa file perantara. Tapi ini sepertinya tidak bagus - orang perlu membuat file tambahan dan merawatnya, belum lagi implementasi tambahan yang diperlukan. (Di sisi positifnya, bagaimanapun, itu bisa menjadi penyelamatan untuk kasus-kasus di mana jumlah file sebagai argumen mulai menyebabkan masalah dengan panjang baris perintah ...)


Pada akhirnya, izinkan saya mengingatkan Anda lagi bahwa find+ xargs(dan sejenisnya) trik tidak akan berfungsi dalam kasus saya. Untuk kesederhanaan deskripsi, saya hanya menunjukkan satu argumen. Tetapi kasus saya yang sebenarnya lebih terlihat seperti ini:

$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES

Jadi melakukan xargsdari satu pencarian masih membuat saya berurusan dengan yang lain ...

Adam Badura
sumber

Jawaban:

13

Gunakan array.

Jika Anda tidak perlu menangani kemungkinan baris baru dalam nama file Anda, maka Anda bisa lolos

mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)

kemudian

./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"

Jika Anda lakukan perlu pegangan baris dalam nama file, dan memiliki pesta> = 4.4, Anda dapat menggunakan -print0dan -d ''untuk nol-mengakhiri nama selama konstruksi array yang:

mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)

(dan juga untuk XYZ_FILES). Jika Anda tidak memiliki bash yang lebih baru, maka Anda dapat menggunakan loop baca null-dihentikan untuk menambahkan nama file ke array misalnya

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
Steeldriver
sumber
Luar biasa! Saya sedang berpikir tentang array. Tetapi entah bagaimana saya tidak menemukan apa-apa tentang itu mapfile(atau sinonimnya readarray). Tapi itu berhasil!
Adam Badura
Namun Anda bisa memperbaikinya sedikit. Versi Bash <4.4 (yang kebetulan saya miliki ...) dengan sebuah whileloop tidak menghapus array. Yang berarti bahwa jika tidak ada file yang ditemukan array tidak terdefinisi. Sementara jika sudah ditentukan file baru akan ditambahkan (bukan menggantikan yang lama). Tampaknya menambahkan declare -a ABC_FILES='()';sebelum berhasil while. (Sementara menambahkan ABC_FILES='()';tidak.)
Adam Badura
Juga apa < <artinya di sini? Apakah sama dengan <<? Saya tidak berpikir untuk mengubahnya <<menghasilkan kesalahan sintaks ("token tak terduga` ('"). Jadi apa itu dan bagaimana cara kerjanya?
Adam Badura
Peningkatan lain (sepanjang penggunaan khusus saya) adalah untuk membangun array lain. Jadi kita punya itu ABC_FILES. Ini baik saja. Tetapi berguna juga untuk membuat ABS_ARGSarray kosong jika ABC_FILESkosong atau array ('--abc-files' "${ABC_FILES[@]}"). Dengan cara ini nanti saya bisa menggunakannya seperti ini: ./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"dan pastikan itu akan bekerja dengan benar terlepas dari mana (jika ada) dari grup kosong. Atau untuk menyatakannya secara berbeda: cara ini --abc-files(dan --xyz-files) akan disediakan hanya jika diikuti oleh beberapa jalur aktual.
Adam Badura
1
@AdamBadura: while read ... done < <(find blah)adalah pengalihan shell normal <dari file khusus yang dibuat oleh SUBSTITUSI PROSES . Ini berbeda dari pemipaan find blah | while read ... donekarena pipa menjalankan whileloop dalam subkulit sehingga var (s) yang ditetapkan di dalamnya tidak dipertahankan untuk perintah berikutnya.
dave_thompson_085
3

Anda dapat menggunakan IFS = baris baru (dengan asumsi tidak ada nama file yang mengandung baris baru) tetapi Anda harus mengaturnya di kulit luar SEBELUM substitusi:

$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker

Dengan zshtetapi tidak bashAnda dapat menggunakan null $'\0'juga. Bahkan di dalam bashkamu dapat menangani baris baru jika ada satu karakter yang cukup aneh yang tidak pernah digunakan seperti

 IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...

Namun, pendekatan ini tidak menangani permintaan tambahan yang Anda buat di komentar pada jawaban @ steeldriver untuk menghilangkan --afile jika menemukan kosong.

dave_thompson_085
sumber
Jadi seperti yang saya pahami di Bash, tidak ada cara untuk memaksa IFSuntuk berpisah null?
Adam Badura
@AdamBadura: Saya cukup yakin tidak; bash tidak mengizinkan null byte dalam variabel apa pun, termasuk IFS. Catatan yang read -d ''digunakan dalam metode steeldriver adalah string kosong yang bukan berisi byte nol. (Dan bagaimanapun juga, opsi perintah bukanlah var.)
dave_thompson_085
Anda juga harus menonaktifkan globbing ( set -o noglob) sebelum menggunakan operator split + glob tersebut (kecuali dalam zsh).
Stéphane Chazelas
@ AdamBadura Ya, dalam bash, null sama persis dengan $'\0'dan juga sebagai ''.
Isaac
1

Saya tidak yakin saya mengerti mengapa Anda menyerah xargs.

Jadi melakukan xargsdari satu pencarian masih membuat saya berurusan dengan yang lain ...

String --xyz-fileshanyalah salah satu dari banyak argumen dan tidak ada alasan untuk menganggapnya spesial sebelum ditafsirkan oleh program Anda. Saya pikir Anda dapat melewatinya di xargsantara kedua findhasil:

{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files
Kamil Maciorowski
sumber
Kamu benar! Ini juga berfungsi! Namun perhatikan bahwa Anda ketinggalan -print0di urutan kedua find. Juga jika akan cara ini saya akan menempatkan --abc-filessebagai echojuga - hanya untuk konsistensi.
Adam Badura
Pendekatan ini tampaknya lebih sederhana dan agak lebih satu-liner daripada pendekatan array. Namun itu akan memerlukan beberapa logika tambahan untuk menutup kasus bahwa jika tidak ada .abcfile maka harus juga tidak ada --abc-files(sama dengan .xyz). The solusi berbasis-array oleh steeldriver juga membutuhkan logika ekstra untuk itu tapi logika yang sepele ada sementara mungkin tidak begitu sepele disini menghancurkan keuntungan utama dari solusi ini - kesederhanaan.
Adam Badura
Juga saya tidak benar-benar yakin tapi saya menganggap bahwa xargstidak akan pernah mencoba untuk membagi argumen dan membuat beberapa perintah, bukan satu, kecuali itu secara eksplisit diperintahkan untuk melakukannya dengan -L, --max-lines( -l), --max-args( -n) atau --max-chars( -s) argumen. Apakah saya benar? Atau ada beberapa default? Karena program saya tidak akan menangani perpecahan seperti itu dengan benar dan saya lebih suka memiliki kegagalan untuk menyebutnya ...
Adam Badura
1
@AdamBadura Hilang -print0- diperbaiki, terima kasih. Saya tidak tahu semua jawaban tetapi saya setuju solusi saya membuatnya sulit untuk memasukkan logika tambahan. Saya mungkin akan pergi dengan array sendiri, sekarang ketika saya tahu pendekatan ini. Jawaban saya bukan untuk Anda. Anda sudah menerima jawaban lain dan saya berasumsi masalah Anda terpecahkan. Saya hanya ingin menunjukkan bahwa Anda dapat melewati argumen dari berbagai sumber melalui xargs, yang tidak jelas pada pandangan pertama. Anda dapat memperlakukannya sebagai bukti konsep. Sekarang kita semua tahu beberapa pendekatan berbeda dan kita dapat secara sadar memilih apa yang cocok untuk kita dalam setiap kasus tertentu.
Kamil Maciorowski
Ya, saya sudah menerapkan solusi berbasis array dan berfungsi seperti pesona. Saya terutama bangga betapa bersihnya berurusan dengan opsionalitas (jika tidak ada file maka tidak ada --abc-files). Tapi Anda benar - ada baiknya mengetahui alternatif Anda! Apalagi saya salah mengira itu tidak mungkin.
Adam Badura