bash - bisakah saya melakukannya: temukan ... -exec this && that?

23

Apakah ada cara untuk secara logis menggabungkan dua perintah shell yang dipanggil dengan find - exec ?

Sebagai contoh untuk mencetak semua file .csv yang berisi string foo bersama dengan kejadiannya, saya ingin melakukan:

find . -iname \*.csv -exec grep foo {} && echo {} \;

tapi bash mengeluh dengan "argumen yang hilang ke '-exec'"

Marcus Junius Brutus
sumber
2
Anda dapat menggunakan 2 -execsecara berurutan atau menggunakan satu -exec sh -c 'grep foo "$0" && printf %s\\n "$0"' {} \;.
jw013
Ini telah membuat saya tersandung berulang kali: Saya selalu berharap bahwa argumen pertama yang diteruskan ke sh(dalam hal ini {}) akan $1dan $0akan menjadi sesuatu seperti sh. Tetapi pada kenyataannya, Anda benar, argumen pertama muncul sebagai $0. Memiliki argumen pertama menjadi nama perintah pemanggilan hanyalah sebuah konvensi, yang tidak secara otomatis ditegakkan dalam kasus ini.
dubiousjim
Mungkin harus digabung dengan pertanyaan ini: unix.stackexchange.com/q/18077/4801
dubiousjim

Jawaban:

24

-execadalah predikat yang menjalankan perintah (bukan shell) dan mengevaluasi true atau false berdasarkan hasil dari perintah (status keluar nol atau tidak nol).

Begitu:

find . -iname '*.csv' -exec grep foo {} \; -print

akan mencetak path file jika grepmenemukan foo dalam file. Alih-alih -printAnda dapat menggunakan -execpredikat lain atau predikat lain

find . -iname '*.csv' -exec grep foo {} \; -exec echo {} \;

Lihat juga !dan -otemukan operator untuk negasi dan atau .

Atau, Anda dapat memulai shell sebagai:

find . -iname '*.csv' -exec sh -c '
   grep foo "$1" && echo "$1"' sh {} \;

Atau untuk menghindari keharusan memulai shell untuk setiap file:

find . -iname '*.csv' -exec sh -c '
  for i do
    grep foo "$i" && echo "$i"
  done' sh {} +
Stéphane Chazelas
sumber
10

Masalah yang Anda hadapi adalah shell pertama-tama mem-parsing baris perintah, dan melihat dua perintah sederhana yang dipisahkan oleh &&operator:, find . -iname \*.csv -exec grep foo {}dan echo {} \;. Mengutip &&( find . -iname \*.csv -exec grep foo {} '&&' echo {} \;) bypasses itu, tapi sekarang perintah dieksekusi oleh findadalah sesuatu seperti grepdengan argumen foo, wibble.csv, &&, echodan wibble.csv. Anda perlu menginstruksikan findmenjalankan shell yang akan menafsirkan &&operator:

find . -iname \*.csv -exec sh -c 'grep foo "$0" && echo "$0"' {} \;

Perhatikan bahwa argumen pertama setelahnya sh -c SOMECOMMANDadalah $0, bukan $1.

Anda dapat menghemat waktu startup dari proses shell untuk setiap file dengan mengelompokkan doa perintah dengan -exec … +. Untuk memudahkan pemrosesan, berikan beberapa nilai dummy $0sehingga "$@"menyebutkan nama file.

find . -iname \*.csv -exec sh -c 'for x in "$@"; do grep foo "$x" && echo "$x"; done' \ {} +

Jika perintah shell hanya dipisahkan oleh dua program &&, finddapat melakukan pekerjaan itu sendiri: tulis dua -exectindakan berurutan , dan yang kedua hanya akan dieksekusi jika yang pertama keluar dengan status 0.

find . -iname \*.csv -exec grep foo {} \; -exec echo {} \;

(Saya berasumsi itu grepdan echohanya untuk tujuan ilustrasi, karena -exec echodapat digantikan oleh -printdan output yang dihasilkan tidak terlalu berguna.)

Gilles 'SANGAT berhenti menjadi jahat'
sumber
2
Saya cenderung menghindari menggunakan "$ 0" untuk itu, karena ini juga digunakan oleh shell untuk menampilkan pesan kesalahan. Misalnya, Anda dapat melihat ./some-file: grep: command not foundpesan kesalahan yang membingungkan . -exec find sh -c '... "$1"' sh {} \;tidak akan memiliki masalah. Ada kesalahan ketik (terkait) di perintah find kedua Anda.
Stéphane Chazelas
3

Dalam kasus khusus ini saya akan lakukan:

find . -iname \*.csv -exec grep -l foo \{\} \;

Atau jika Anda memiliki ACK :

ack -al -G '.*\.csv' foo

Untuk menjawab pertanyaan Anda yang sebenarnya, sesuatu seperti ini dapat bekerja:

find . -iname \*.csv -exec sh -c "grep foo {} && echo {}" \;

Dennis Kaarsemaker
sumber
3
Yang terakhir menemukan perintah tidak hanya non-portabel tetapi juga sangat berbahaya karena jalur file akhirnya dievaluasi sebagai kode shell.
Stéphane Chazelas
Semua alasan lagi untuk mencoba dan menghindarinya dengan menggunakan ack / grep dengan benar :)
Dennis Kaarsemaker
1
Alternatif jw013 (diberikan dalam komentar untuk pertanyaan) lebih aman dalam hal ini. (Hanya memperhatikan bahwa jawaban Gilles dan Stephane menggunakan teknik yang sama.)
dubiousjim