Bagaimana cara saya membuat find gagal jika -exec gagal?

29

Ketika saya menjalankan perintah ini di shell (dalam direktori non-kosong):

find . -exec invalid_command_here {} \;

Saya mendapatkan ini:

find: invalid_command_here: No such file or directory
find: invalid_command_here: No such file or directory
find: invalid_command_here: No such file or directory

(dan seterusnya untuk setiap file)

Saya harus findgagal setelah kesalahan pertama. Apakah ada cara untuk membuatnya bekerja? Saya tidak dapat menggunakan xargs, karena saya memiliki spasi di jalur saya, tetapi saya perlu skrip memanggil ini untuk mengembalikan kode kesalahan.

Steven Fisher
sumber

Jawaban:

34

Ini adalah batasan find. Standar POSIX menentukan bahwa status pengembalian findadalah 0 kecuali terjadi kesalahan saat melintasi direktori; status pengembalian dari perintah yang dieksekusi tidak masuk ke dalamnya.

Anda dapat membuat perintah menulis statusnya ke file atau ke deskriptor:

find_status_file=$(mktemp findstatus)
: >"$find_status_file"
find  -exec sh -c 'trap "echo \$?" EXIT; invalid_command "$0"' {} \;
if [ -s "$find_status_file" ]; then
  echo 1>&2 "An error occurred"
fi
rm -f "$find_status_file"

Metode lain, seperti yang Anda temukan , adalah menggunakan xargs. The xargsperintah selalu memproses semua file, tapi mengembalikan status 1 jika salah satu perintah pengembalian status nol.

find  -print0 | xargs -0 -n1 invalid_command

Namun metode lain adalah menghindari finddan menggunakan globbing rekursif dalam shell sebagai gantinya: **/berarti kedalaman subdirektori. Ini membutuhkan versi 4 atau lebih dari bash; macOS macet di versi 3.x sehingga Anda harus menginstalnya dari koleksi port. Gunakan set -euntuk menghentikan skrip pada perintah pertama mengembalikan status bukan nol.

shopt -s globstar
set -e
for x in **/*.xml; do invalid_command "$x"; done

Berhati-hatilah bahwa dalam bash 4.0 hingga 4.2, ini berfungsi tetapi melintasi tautan simbolik ke direktori, yang biasanya tidak diinginkan.

Jika Anda menggunakan zsh sebagai ganti bash, globbing rekursif bekerja di luar kotak tanpa gotcha. Zsh tersedia secara default di OSX / macOS. Di zsh, Anda bisa menulis

set -e
for x in **/*.xml; do invalid_command "$x"; done
Gilles 'SANGAT berhenti menjadi jahat'
sumber
The xargsPendekatan bekerja pada umumnya tapi entah bagaimana istirahat pada bash -cperintah. Sebagai contoh: find . -name '*.xml' -print0 | xargs -0 -n 1 -I '{}' bash -c "foo {}". Ini dieksekusi beberapa kali sedangkan find . -name '2*.xml' -print0 | xargs -0 -n 1 -I '{}' foo {}dieksekusi sekali dan gagal. Ada yang tahu kenapa?
DKroot
@Droot Jangan pernah gunakan {}di dalam bash -c. Ini mengambil nama file dan menyisipkannya langsung di dalam perintah shell. Jika nama file berisi karakter yang memiliki arti khusus di shell seperti spasi, shell mengartikan karakter khusus ini seperti itu. Jika Anda membutuhkan shell, sampaikan {}sebagai argumen yang terpisah, misalnya bash -c 'foo "$0"' {}(perhatikan juga tanda kutip ganda di sekitar $0).
Gilles 'SO- stop being evil'
Oke, dengan mengutip pertanyaan, mengapa hal berikut tidak berhenti pada kesalahan pertama ?? find . -name '*' -print0 | xargs -0 -n 1 -I '{}' bash -c 'foo "$0"' {}
DKroot
@Droot Mengapa berhenti pada kesalahan? xargs selalu menjalankan perintah pada semua item.
Gilles 'SANGAT berhenti menjadi jahat'
Saya mencoba menggunakan jawaban ini: pendekatan xargs ( find . -print0 | xargs -0 -n1 invalid_command). Ini berhenti pada kesalahan pertama benar: find . -name '*' -print0 | xargs -0 -n 1 -I '{}' foo {}. Besar! Tetapi pendekatan yang sama tidak bekerja dengan bash -c(di atas). Satu-satunya perbedaan antara keduanya adalah bash -c.
DKroot
18

Saya bisa menggunakan ini sebagai gantinya:

find . -name *.xml -print0 | xargs -n 1 -0 invalid_command
Steven Fisher
sumber
4

xargsadalah salah satu pilihan. Namun, ini sebenarnya mudah untuk dilakukan dengan findmenggunakan +alih-alih\;

-exec  utility_name  [argument ...]   {} +

Dari dokumentasi POSIX :

Jika ekspresi primer diselingi oleh tanda tambah, primer harus selalu dievaluasi sebagai benar, dan nama path yang dievaluasi primer harus diagregasi ke dalam set. Utilitas utility_name harus dipanggil sekali untuk setiap set nama path yang diagregasi. Setiap doa harus dimulai setelah pathname terakhir di set dikumpulkan, dan harus diselesaikan sebelum utilitas find keluar dan sebelum pathname pertama di set berikutnya (jika ada) dikumpulkan untuk primer ini, tetapi dinyatakan tidak ditentukan apakah permintaan terjadi sebelum, selama, atau setelah evaluasi pendahuluan lainnya. Jika setiap pemanggilan mengembalikan nilai bukan nol sebagai status keluar, utilitas find akan mengembalikan status keluar bukan nol.Argumen yang hanya berisi dua karakter “{}” harus diganti dengan sekumpulan nama path teragregasi, dengan masing-masing nama path dilewatkan sebagai argumen terpisah ke utilitas yang dipanggil dalam urutan yang sama seperti yang digabungkan. Ukuran kumpulan dua atau lebih nama path harus dibatasi sedemikian rupa sehingga pelaksanaan utilitas tidak menyebabkan batas {ARG_MAX} sistem terlampaui. Jika lebih dari satu argumen yang hanya mengandung dua karakter “{}” ada, perilaku tersebut tidak ditentukan.

Orang Swiss
sumber