Memahami opsi -exec dari `find`

53

Saya menemukan diri saya terus mencari sintaks

find . -name "FILENAME"  -exec rm {} \;

terutama karena saya tidak melihat bagaimana tepatnya -execbagian itu berfungsi. Apa arti dari kawat gigi, garis miring terbalik dan titik koma? Apakah ada kasus penggunaan lain untuk sintaksis itu?

Zsolt Szilagy
sumber
11
@ Pilipos: Saya mengerti maksud Anda. Harap diingat bahwa halaman manual adalah referensi, yaitu berguna bagi mereka yang memahami masalah untuk mencari sintaksis. Untuk seseorang yang baru dalam topik ini, mereka sering kali bersifat rahasia dan formal agar bermanfaat. Anda akan menemukan bahwa jawaban yang diterima adalah sekitar 10 kali selama entri halaman manual, dan itu karena suatu alasan.
Zsolt Szilagy
6
Bahkan manhalaman POSIX lama membaca Utilitas_name atau argumen yang hanya mengandung dua karakter "{}" harus diganti oleh pathname saat ini , yang tampaknya cukup bagi saya. Selain itu memiliki contoh dengan -exec rm {} \;, seperti dalam pertanyaan Anda. Di masa saya, hampir tidak ada sumber daya lain selain "tembok abu-abu besar", buku-buku manhalaman yang dicetak (kertas lebih ceper daripada penyimpanan). Jadi saya tahu bahwa ini cukup untuk seseorang yang baru dalam topik ini. Namun pertanyaan terakhir Anda cukup adil untuk ditanyakan di sini. Sayangnya @Kusalananda maupun saya sendiri tidak punya jawaban untuk itu.
Philippos
1
Comeon @Philippos. Apakah Anda benar-benar memberi tahu Kusalananda bahwa ia tidak memperbaiki halaman manual? :-)
Zsolt Szilagy
1
@allo Meskipun xargsterkadang berguna, finddapat melewati beberapa argumen jalur untuk memerintahkan tanpa itu. -exec command... {} +(dengan +bukannya \;) melewati banyak jalur setelah command...sesuai (masing-masing OS memiliki batas sendiri pada berapa lama baris perintah dapat). Dan seperti xargs, yang +bentuk -terminated dari find's -exectindakan juga akan berjalan command...beberapa kali dalam kasus yang jarang terjadi bahwa ada terlalu banyak jalan agar sesuai dalam batas.
Eliah Kagan
2
@ ZsoltSzilagy Saya tidak mengatakan itu atau bermaksud itu. Dia memberi Anda makan dengan sangat baik, saya pikir Anda sudah cukup umur untuk makan sendiri. (-;
Philippos

Jawaban:

90

Jawaban ini datang di bagian berikut:

  • Penggunaan dasar -exec
  • Menggunakan -execdalam kombinasi dengansh -c
  • Menggunakan -exec ... {} +
  • Menggunakan -execdir

Penggunaan dasar -exec

The -execpilihan mengambil utilitas eksternal dengan argumen opsional sebagai argumen dan mengeksekusi itu.

Jika string {}ada di mana saja dalam perintah yang diberikan, setiap instance akan digantikan oleh pathname yang saat ini sedang diproses (misalnya ./some/path/FILENAME). Dalam kebanyakan shell, kedua karakter {}tidak perlu dikutip.

Perintah perlu diakhiri dengan ;untuk findmengetahui di mana ia berakhir (karena mungkin ada opsi lebih lanjut setelah itu). Untuk melindungi ;shell, shell harus dikutip sebagai \;atau ';', jika tidak shell akan melihatnya sebagai akhir dari findperintah.

Contoh ( \pada akhir dua baris pertama hanya untuk kelanjutan baris):

find . -type f -name '*.txt'      \
   -exec grep -q 'hello' {} ';'   \
   -exec cat {} ';'

Ini akan menemukan semua file biasa ( -type f) yang namanya cocok dengan pola *.txtdi atau di bawah direktori saat ini. Kemudian akan menguji apakah string helloterjadi di salah satu file yang ditemukan menggunakan grep -q(yang tidak menghasilkan output apa pun, hanya status keluar). Untuk file-file yang berisi string, catakan dieksekusi untuk menampilkan isi file ke terminal.

Masing-masing -execjuga bertindak seperti "tes" pada nama path yang ditemukan oleh find, sama seperti -typedan -nametidak. Jika perintah mengembalikan status keluar nol (menandakan "sukses"), bagian selanjutnya dari findperintah dipertimbangkan, jika tidak, findperintah akan dilanjutkan dengan nama path berikutnya. Ini digunakan dalam contoh di atas untuk menemukan file yang berisi string hello, tetapi untuk mengabaikan semua file lainnya.

Contoh di atas menggambarkan dua kasus penggunaan paling umum -exec:

  1. Sebagai ujian untuk lebih membatasi pencarian.
  2. Untuk melakukan beberapa tindakan pada pathname yang ditemukan (biasanya, tetapi tidak harus, di akhir findperintah).

Menggunakan -execdalam kombinasi dengansh -c

Perintah yang -execdapat dieksekusi terbatas pada utilitas eksternal dengan argumen opsional. Untuk menggunakan built-in shell, fungsi, kondisional, jalur pipa, pengalihan dll secara langsung dengan -exectidak dimungkinkan, kecuali dibungkus dengan sesuatu seperti sh -cshell anak.

Jika bashfitur diperlukan, maka gunakan bash -cdi tempat sh -c.

sh -cberjalan /bin/shdengan skrip yang diberikan pada baris perintah, diikuti oleh argumen baris perintah opsional untuk skrip itu.

Contoh sederhana penggunaannya sh -csendiri, tanpa find:

sh -c 'echo  "You gave me $1, thanks!"' sh "apples"

Ini meneruskan dua argumen ke skrip shell anak:

  1. String sh. Ini akan tersedia sebagai $0di dalam skrip, dan jika shell internal menampilkan pesan kesalahan, ia akan awalan dengan string ini.

  2. Argumen applestersebut tersedia seperti $1dalam skrip, dan jika ada lebih banyak argumen, maka ini akan tersedia sebagai $2, $3dll. Mereka juga akan tersedia dalam daftar "$@"(kecuali $0yang tidak akan menjadi bagian dari "$@").

Ini berguna dalam kombinasi dengan -execkarena memungkinkan kita untuk membuat skrip kompleks yang bertindak sewenang-wenang pada nama path yang ditemukan oleh find.

Contoh: Temukan semua file biasa yang memiliki akhiran nama file tertentu, dan ubah akhiran nama file itu ke beberapa akhiran lainnya, di mana akhiran disimpan dalam variabel:

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'

Di dalam skrip internal, $1akan menjadi string text, $2akan menjadi string txtdan $3akan menjadi apa pun pathname yang findditemukan untuk kita. Ekspansi parameter ${3%.$1}akan mengambil pathname dan menghapus akhiran .textdarinya.

Atau, menggunakan dirname/ basename:

find . -type f -name "*.$from" -exec sh -c '
    mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'

atau, dengan variabel yang ditambahkan di skrip internal:

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2; pathname=$3
    mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'

Perhatikan bahwa dalam variasi terakhir ini, variabel fromdan todi shell anak berbeda dari variabel dengan nama yang sama di skrip eksternal.

Di atas adalah cara yang benar memanggil script kompleks sewenang-wenang dari -execdengan find. Menggunakan finddalam lingkaran seperti

for pathname in $( find ... ); do

rentan terhadap kesalahan dan tidak tangguh (pendapat pribadi). Ini adalah pemisahan nama file pada spasi putih, memanggil globbing nama file, dan juga memaksa shell untuk memperluas hasil lengkap findsebelum bahkan menjalankan iterasi pertama dari loop.

Lihat juga:


Menggunakan -exec ... {} +

Pada ;akhirnya dapat diganti dengan +. Ini menyebabkan finduntuk mengeksekusi perintah yang diberikan dengan sebanyak mungkin argumen (ditemukan nama path) daripada satu untuk setiap nama path yang ditemukan. String {} harus terjadi tepat sebelum +ini berfungsi .

find . -type f -name '*.txt' \
   -exec grep -q 'hello' {} ';' \
   -exec cat {} +

Di sini, findakan mengumpulkan nama path yang dihasilkan dan mengeksekusi catpada sebanyak mungkin dari mereka sekaligus.

find . -type f -name "*.txt" \
   -exec grep -q "hello" {} ';' \
   -exec mv -t /tmp/files_with_hello/ {} +

Demikian juga di sini, mvakan dieksekusi beberapa kali mungkin. Contoh terakhir ini membutuhkan GNU mvdari coreutils (yang mendukung -topsi).

Menggunakan -exec sh -c ... {} +juga merupakan cara yang efisien untuk mengulang set nama path dengan skrip yang sewenang-wenang.

Dasar-dasarnya sama dengan ketika menggunakan -exec sh -c ... {} ';', tetapi skrip sekarang membutuhkan daftar argumen yang jauh lebih lama. Ini dapat diulang dengan mengulang "$@"di dalam skrip.

Contoh kami dari bagian terakhir yang mengubah akhiran nama file:

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2
    shift 2  # remove the first two arguments from the list
             # because in this case these are *not* pathnames
             # given to us by find
    for pathname do  # or:  for pathname in "$@"; do
        mv "$pathname" "${pathname%.$from}.$to"
    done' sh "$from" "$to" {} +

Menggunakan -execdir

Ada juga -execdir(diimplementasikan oleh sebagian besar findvarian, tetapi bukan opsi standar).

Ini berfungsi seperti -execdengan perbedaan bahwa perintah shell yang diberikan dieksekusi dengan direktori pathname yang ditemukan sebagai direktori kerjanya saat ini dan itu {}akan berisi nama samaran dari pathname yang ditemukan tanpa pathnya (tetapi GNU findmasih akan awalan dengan nama basenya ./, sementara BSD findtidak akan melakukan itu).

Contoh:

find . -type f -name '*.txt' \
    -execdir mv {} done-texts/{}.done \;

Ini akan memindahkan setiap *.txtfile -file yang ditemukan ke done-textssubdirektori yang sudah ada di direktori yang sama dengan tempat file ditemukan . File juga akan diganti namanya dengan menambahkan akhiran .doneke dalamnya.

Ini akan sedikit lebih sulit untuk dilakukan -execkarena kita harus mengeluarkan nama file dari file yang ditemukan {}untuk membentuk nama file yang baru. Kami juga memerlukan nama direktori dari {}untuk menemukan done-textsdirektori dengan benar.

Dengan -execdir, beberapa hal seperti ini menjadi lebih mudah.

Operasi yang sesuai menggunakan -execalih-alih -execdirharus menggunakan shell anak:

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
    done' sh {} +

atau,

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "${name%/*}/done-texts/${name##*/}.done"
    done' sh {} +
Kusalananda
sumber
7
-execmengambil program dan argumen dan menjalankannya; beberapa perintah shell hanya terdiri dari program dan argumen tetapi banyak yang tidak. Perintah shell dapat mencakup pengalihan dan perpipaan; -exectidak dapat (meskipun keseluruhan finddapat dialihkan). Perintah shell dapat menggunakan ; && ifdll; -exectidak bisa, walaupun -a -obisa melakukan beberapa. Perintah shell bisa berupa fungsi alias atau shell, atau builtin; -exectidak bisa. Perintah shell dapat memperluas vars; -exectidak dapat (meskipun kulit terluar yang menjalankan findkaleng). Perintah shell dapat menggantikan secara $(command)berbeda setiap kali; -exectidak bisa. ...
dave_thompson_085
... Perintah shell dapat glob, -exectidak bisa - meskipun finddapat beralih di atas file dengan cara yang sama seperti kebanyakan glob, jadi itu jarang diinginkan.
dave_thompson_085
@ dave_thompson_085 Tentu saja, perintah shell dapat menjadi shdirinya sendiri, yang sepenuhnya mampu melakukan semua hal
Tavian Barnes
2
Mengatakan itu adalah perintah shell salah di sini, find -exec cmd arg \;tidak meminta shell untuk menginterpretasikan baris perintah shell, itu berjalan execlp("cmd", "arg")langsung, bukan execlp("sh", "-c", "cmd arg")(yang shell akhirnya akan melakukan yang setara dengan execlp("cmd", "arg")jika cmdtidak dibangun).
Stéphane Chazelas
2
Anda bisa mengklarifikasi bahwa semua findargumen setelah -execdan hingga ;atau +membuat perintah untuk mengeksekusi bersama dengan argumennya, dengan setiap instance {}argumen diganti dengan file saat ini (dengan ;), dan {}sebagai argumen terakhir sebelum +diganti dengan daftar file sebagai argumen terpisah (dalam {} +kasus ini). TKI -execmengambil beberapa argumen, diakhiri oleh a ;atau {} +.
Stéphane Chazelas