temukan -exec fungsi shell di Linux?

185

Apakah ada cara untuk findmenjalankan fungsi yang saya definisikan di shell? Sebagai contoh:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;

Hasilnya adalah:

find: dosomething: No such file or directory

Apakah ada cara untuk mendapatkan find's -execuntuk melihat dosomething?

alxndr
sumber

Jawaban:

262

Karena hanya shell yang tahu cara menjalankan fungsi shell, Anda harus menjalankan shell untuk menjalankan fungsi. Anda juga harus menandai fungsi Anda untuk ekspor export -f, jika tidak, subkulit tersebut tidak akan mewarisinya:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;
Adam Rosenfield
sumber
7
Kamu mengalahkanku. Ngomong-ngomong Anda bisa meletakkan kurung di dalam tanda kutip alih-alih menggunakan $0.
Dijeda sampai pemberitahuan lebih lanjut.
20
Jika Anda menggunakannya find . -exec bash -c 'dosomething "$0"' {} \;akan menangani spasi (dan karakter aneh lainnya) dalam nama file ...
Gordon Davisson
3
@alxndr: itu akan gagal pada nama file dengan tanda kutip ganda, tanda kutip belakang, tanda dolar, beberapa kombo pelarian, dll ...
Gordon Davisson
4
Perhatikan juga bahwa fungsi apa pun yang mungkin dipanggil oleh fungsi Anda tidak akan tersedia kecuali jika Anda juga mengekspornya.
hraban
3
Ini export -fhanya akan berfungsi di beberapa versi bash. Itu bukan posix, bukan crossplatforn, /bin/shakan memiliki kesalahan dengan itu
Роман Коптев
120
find . | while read file; do dosomething "$file"; done
Jac
sumber
10
Solusi bagus Tidak memerlukan ekspor fungsi atau bermain-main untuk menghindari argumen dan mungkin lebih efisien karena tidak memunculkan subkulit untuk menjalankan setiap fungsi.
Tom
19
Perlu diingat, bahwa itu akan merusak nama file yang mengandung baris baru.
chepner
3
Ini lebih "shell'ish" karena variabel global dan fungsi Anda akan tersedia, tanpa membuat shell / lingkungan yang sama sekali baru setiap kali. Belajar ini dengan cara yang sulit setelah mencoba metode Adam dan mengalami berbagai macam masalah lingkungan. Metode ini juga tidak merusak shell pengguna Anda saat ini dengan semua ekspor dan membutuhkan lebih sedikit disiplin.
Ray Foss
JAUH LEBIH CEPAT daripada jawaban yang diterima tanpa sub shell! BAGUS! Terima kasih!
Bill Heller
2
juga saya memperbaiki masalah saya dengan mengubah while readfor for loop; for item in $(find . ); do some_function "${item}"; done
user5359531
22

Jawaban Jak di atas bagus tetapi memiliki beberapa jebakan yang mudah diatasi:

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

Ini menggunakan null sebagai pembatas alih-alih umpan baris, sehingga nama file dengan umpan baris akan berfungsi. Itu juga menggunakan -rbendera yang menonaktifkan backslash melarikan diri, tanpa itu backslash dalam nama file tidak akan berfungsi. Itu juga membersihkan IFSsehingga potensi spasi putih tertinggal dalam nama tidak dibuang.

piyama
sumber
1
Ini bagus untuk /bin/bashtetapi tidak akan bekerja /bin/sh. Sayang sekali.
Роман Коптев
@ РоманКоптев Betapa beruntungnya setidaknya itu bekerja di / bin / bash.
sdenham
21

Tambahkan kutipan {}seperti yang ditunjukkan di bawah ini:

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

Ini memperbaiki kesalahan apa pun karena karakter khusus dikembalikan oleh find, misalnya file dengan tanda kurung dalam namanya.

Wagner
sumber
1
Ini bukan cara yang benar untuk digunakan {}. Ini akan mematahkan untuk nama file yang berisi tanda kutip ganda. touch '"; rm -rf .; echo "I deleted all you files, haha. Ups.
gniourf_gniourf
Ya, ini sangat buruk. Itu bisa dimanfaatkan dengan suntikan. Sangat tidak aman. Jangan gunakan ini!
Dominik
1
@kdubs: Gunakan $ 0 (tanda kutip) di dalam command-string dan berikan nama file sebagai argumen pertama: -exec bash -c 'echo $0' '{}' \;Perhatikan bahwa ketika menggunakan bash -c, $ 0 adalah argumen pertama, bukan nama skrip.
sdenham
10

Memproses hasil dalam jumlah besar

Untuk meningkatkan efisiensi, banyak orang menggunakan xargsuntuk memproses hasil dalam jumlah besar, tetapi sangat berbahaya. Karena itu ada metode alternatif yang diperkenalkan ke dalam melakukan findhasil dalam jumlah besar.

Catat bahwa metode ini mungkin datang dengan beberapa peringatan seperti misalnya persyaratan dalam POSIX- findharus ada {}di akhir perintah.

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +

findakan melewatkan banyak hasil sebagai argumen untuk satu panggilan tunggal bashdan for-loop mengulangi argumen tersebut, menjalankan fungsi dosomethingpada masing-masing argumen tersebut.

Solusi di atas memulai argumen di $1, itulah sebabnya ada _(yang mewakili $0).

Memproses hasil satu per satu

Dengan cara yang sama, saya berpikir bahwa jawaban teratas yang diterima harus diperbaiki

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;

Ini tidak hanya lebih waras, karena argumen harus selalu dimulai $1, tetapi juga menggunakan $0dapat menyebabkan perilaku yang tidak terduga jika nama file dikembalikan dengan findmemiliki arti khusus pada shell.

Dominik
sumber
9

Buat skrip memanggil dirinya sendiri, melewati setiap item yang ditemukan sebagai argumen:

#!/bin/bash

if [ ! $1 == "" ] ; then
   echo "doing something with $1"
   exit 0
fi

find . -exec $0 {} \;

exit 0

Saat Anda menjalankan skrip dengan sendirinya, ia menemukan apa yang Anda cari dan menyebut dirinya melewati setiap hasil temuan sebagai argumen. Ketika skrip dijalankan dengan argumen, ia menjalankan perintah pada argumen dan kemudian keluar.

Mike Maready
sumber
ide keren tapi gaya buruk: menggunakan skrip yang sama untuk dua tujuan. jika Anda ingin mengurangi jumlah file di bin Anda / maka Anda bisa menggabungkan semua skrip Anda menjadi satu yang memiliki klausa kasus besar di awal. solusi yang sangat bersih, bukan?
user829755
belum lagi ini akan gagal find: ‘myscript.sh’: No such file or directoryjika dimulai sebagai bash myscript.sh...
Camusensei
5

Bagi Anda yang mencari fungsi bash yang akan menjalankan perintah yang diberikan pada semua file di direktori saat ini, saya telah mengkompilasi salah satu dari jawaban di atas:

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}

Perhatikan bahwa itu rusak dengan nama file yang berisi spasi (lihat di bawah).

Sebagai contoh, ambil fungsi ini:

world(){
    sed -i 's_hello_world_g' "$1"
}

Katakanlah saya ingin mengubah semua instance hello ke dunia dalam semua file di direktori saat ini. Saya akan lakukan:

toall world

Agar aman dengan simbol apa pun dalam nama file, gunakan:

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}

(tetapi Anda membutuhkan findyang menangani -print0misalnya, GNU find).

Jason Basanese
sumber
3

Saya menemukan cara termudah sebagai berikut, mengulangi dua perintah dalam satu do

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done
edib
sumber
3

Untuk referensi, saya menghindari skenario ini menggunakan:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

Dapatkan Output dari find di file skrip saat ini dan beralih ke output sesuai keinginan Anda. Saya setuju dengan jawaban yang diterima, tetapi saya tidak ingin mengekspos fungsi di luar file skrip saya.

dinesh saini
sumber
ini memiliki batas ukuran
Richard
2

Tidak mungkin menjalankan fungsi seperti itu.

Untuk mengatasinya, Anda dapat menempatkan fungsi Anda dalam skrip shell dan memanggilnya dari find

# dosomething.sh
dosomething () {
  echo "doing something with $1"
}
dosomething $1

Sekarang gunakan di find sebagai:

find . -exec dosomething.sh {} \;
codaddict
sumber
Sedang berusaha menghindari lebih banyak file di ~ / bin saya. Terimakasih Meskipun!
alxndr
Saya dianggap downvoting tetapi solusi itu sendiri tidak buruk. Silakan gunakan kutipan yang benar: dosomething $1=> dosomething "$1"dan mulai file Anda dengan benar denganfind . -exec bash dosomething.sh {} \;
Camusensei
2

Masukkan fungsi dalam file terpisah dan findjalankan untuk itu.

Fungsi-fungsi Shell bersifat internal ke shell-nya; findtidak akan pernah bisa melihat mereka.

Angus
sumber
Kena kau; masuk akal. Sedang mencoba untuk menghindari lebih banyak file di ~ / bin saya.
alxndr
2

Untuk memberikan tambahan dan klarifikasi pada beberapa jawaban lain, jika Anda menggunakan opsi massal untuk execatau execdir( -exec command {} +), dan ingin mengambil semua argumen posisi, Anda perlu mempertimbangkan penanganan $0dengan bash -c. Lebih konkret, pertimbangkan perintah di bawah ini, yang digunakan bash -cseperti yang disarankan di atas, dan cukup gema jalur file yang diakhiri dengan '.wav' dari setiap direktori yang ditemukannya:

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

Manual bash mengatakan:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

Di sini, 'check $@'adalah string perintah, dan _ {}argumen setelah string perintah. Perhatikan bahwa $@adalah parameter posisi khusus dalam bash yang diperluas ke semua parameter posisi mulai dari 1 . Perhatikan juga bahwa dengan -copsi, argumen pertama ditugaskan ke parameter posisi $0. Ini berarti bahwa jika Anda mencoba mengakses semua parameter posisi dengan $@, Anda hanya akan mendapatkan parameter mulai dari $1dan ke atas. Itulah alasan mengapa jawaban Dominik memiliki _, yang merupakan argumen dummy untuk mengisi parameter $0, jadi semua argumen yang kita inginkan tersedia nanti jika kita menggunakan $@ekspansi parameter misalnya, atau untuk loop seperti dalam jawaban itu.

Tentu saja, mirip dengan jawaban yang diterima, bash -c 'shell_function $0 $@'juga akan bekerja dengan melewati secara eksplisit $0, tetapi sekali lagi, Anda harus ingat bahwa $@itu tidak akan berfungsi seperti yang diharapkan.

pemadam kebakaran
sumber
0

Tidak secara langsung, tidak. Find dijalankan dalam proses terpisah, bukan di shell Anda.

Buat skrip shell yang melakukan pekerjaan yang sama dengan fungsi Anda dan temukan -execitu.

Laurence Gonsalves
sumber
Sedang berusaha menghindari lebih banyak file di ~ / bin saya. Terimakasih Meskipun!
alxndr
-2

Saya akan menghindari menggunakan -execsemuanya. Kenapa tidak digunakan xargs?

find . -name <script/command you're searching for> | xargs bash -c
Barry
sumber
Pada saat itu, IIRC sedang dalam upaya untuk mengurangi jumlah sumber daya yang digunakan. Pikirkan menemukan jutaan file kosong dan menghapusnya.
alxndr