Menjalankan fungsi yang ditentukan pengguna dalam menemukan -exec panggilan

25

Saya menggunakan Solaris 10 dan saya telah menguji yang berikut dengan ksh (88), bash (3.00) dan zsh (4.2.1).

Kode berikut tidak menghasilkan hasil apa pun:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

Temuan tidak cocok dengan beberapa file (seperti yang ditunjukkan dengan mengganti -exec ...dengan -print), dan fungsinya berfungsi dengan baik ketika dipanggil di luar dari findpanggilan.

Inilah yang dikatakan man findlaman tentang -exec:

 -exec command Benar jika perintah yang dijalankan mengembalikan a
                     nilai nol sebagai status keluar. Akhir dari
                     perintah harus diselingi oleh yang melarikan diri
                     titik koma (;). Argumen perintah {} adalah
                     diganti dengan pathname saat ini. Jika
                     argumen terakhir ke -exec adalah {} dan Anda
                     tentukan + daripada tanda titik koma (;),
                     perintah dipanggil lebih sedikit, dengan
                     {} digantikan oleh grup nama path. Jika
                     setiap pemanggilan perintah mengembalikan a
                     nilai bukan nol sebagai status keluar, temukan
                     mengembalikan status keluar bukan nol.

Saya mungkin bisa melakukan sesuatu seperti ini:

for f in $(find somedir); do
    foo
done

Tapi saya takut berurusan dengan masalah pemisah lapangan.

Apakah mungkin untuk memanggil fungsi shell (didefinisikan dalam skrip yang sama, jangan repot-repot dengan masalah pelingkupan) dari find ... -exec ...panggilan?

Saya mencobanya dengan keduanya /usr/bin/finddan /bin/finddan mendapatkan hasil yang sama.

rahmu
sumber
apakah Anda mencoba mengekspor fungsi setelah Anda mendeklarasikannya? export -f foo
h3rrmiller
2
Anda harus membuat fungsi skrip eksternal dan memasukkannya ke dalam PATH. Atau, gunakan sh -c '...'dan keduanya mendefinisikan DAN menjalankan fungsi dalam ...bit. Mungkin membantu untuk memahami perbedaan antara fungsi dan skrip .
jw013

Jawaban:

27

A functionadalah lokal untuk sebuah shell, jadi Anda perlu find -execmemunculkan shell dan memiliki fungsi yang didefinisikan dalam shell itu sebelum dapat menggunakannya. Sesuatu seperti:

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bashmemungkinkan seseorang untuk mengekspor fungsi melalui lingkungan dengan export -f, sehingga Anda dapat melakukan (dalam bash):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88harus typeset -fxmengekspor fungsi (bukan melalui lingkungan), tetapi itu hanya dapat digunakan oleh she-bang lebih sedikit skrip yang dieksekusi oleh ksh, jadi tidak dengan ksh -c.

Pilihan lain adalah melakukan:

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

Artinya, gunakan typeset -funtuk membuang definisi foofungsi di dalam skrip inline. Perhatikan bahwa jika foomenggunakan fungsi lain, Anda juga harus membuangnya juga.

Stéphane Chazelas
sumber
2
Bisakah Anda jelaskan mengapa ada dua kejadian kshatau bashdalam -execperintah? Saya mengerti yang pertama, tetapi bukan yang kedua.
daniel kullmann
6
@danielkullmann In bash -c 'some-code' a b c, $0is a, $1is b..., jadi jika Anda ingin $@menjadi a, b, cAnda harus memasukkan sesuatu sebelumnya. Karena $0juga digunakan saat menampilkan pesan kesalahan, itu ide yang baik untuk menggunakan nama shell, atau sesuatu yang masuk akal dalam konteks itu.
Stéphane Chazelas
@ StéphaneChazelas, terima kasih atas jawaban yang bagus.
User9102d82
5

Ini tidak selalu berlaku, tetapi ketika itu, itu adalah solusi sederhana. Setel globstaropsi ( set -o globstardalam ksh93, shopt -s globstardalam bash ≥4 ; aktif secara default di zsh). Lalu gunakan**/ untuk mencocokkan direktori saat ini dan subdirektori secara rekursif.

Misalnya, alih-alih find . -name '*.txt' -exec somecommand {} \;, Anda dapat menjalankan

for x in **/*.txt; do somecommand "$x"; done

Alih-alih find . -type d -exec somecommand {} \;, Anda bisa lari

for d in **/*/; do somecommand "$d"; done

Alih-alih find . -newer somefile -exec somecommand {} \;, Anda bisa lari

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

Ketika **/tidak bekerja untuk Anda (karena shell Anda tidak memilikinya, atau karena Anda memerlukan findopsi yang tidak memiliki analog shell), tentukan fungsi dalam find -execargumen .

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Sepertinya saya tidak dapat menemukan globstaropsi pada versi ksh ( ksh88) yang saya gunakan.
rahmu
@rahmu Memang, ini baru di ksh93. Bukankah Solaris 10 punya ksh93 di suatu tempat?
Gilles 'SANGAT berhenti menjadi jahat'
Menurut posting blog ini, ksh93 telah diperkenalkan di Solaris 11 untuk menggantikan Bourne Shell dan ksh88 ...
rahmu
@rahmu. Solaris memiliki ksh93 untuk sementara sebagai dtksh(ksh93 dengan beberapa ekstensi X11), tetapi versi lama dan mungkin bagian dari paket opsional di mana CDE adalah opsional.
Stéphane Chazelas
Perlu dicatat bahwa globbing rekursif berbeda dari findkarena tidak termasuk dotfiles dan tidak turun ke dotdir dan bahwa itu mengurutkan daftar file (keduanya dapat alamat di zsh melalui kualifikasi globbing). Juga dengan **/*berlawanan dengan ./**/*, nama file dapat dimulai dengan -.
Stéphane Chazelas
4

Gunakan \ 0 sebagai pembatas dan baca nama file ke proses saat ini dari perintah spawned, seperti:

foo() {
  printf "Hello {%s}\n" "$1"
}

while read -d '' filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

Apa yang terjadi di sini:

  • read -d '' membaca hingga byte \ 0 berikutnya, jadi Anda tidak perlu khawatir tentang karakter aneh dalam nama file.
  • demikian pula, -print0gunakan \ 0 untuk mengakhiri setiap nama file yang dihasilkan, bukan \ n.
  • cmd2 < <(cmd1)sama seperti cmd1 | cmd2kecuali bahwa cmd2 dijalankan di shell utama dan bukan subshell.
  • panggilan ke foo dialihkan dari /dev/nulluntuk memastikan tidak sengaja membaca dari pipa.
  • $filename dikutip sehingga shell tidak mencoba untuk membagi nama file yang berisi spasi putih.

Sekarang, read -ddan <(...)berada di zsh, bash dan ksh 93u, tapi saya tidak yakin tentang versi ksh sebelumnya.

aecolley
sumber
4

jika Anda ingin proses anak, dihasilkan dari skrip Anda, untuk menggunakan fungsi shell yang telah ditentukan sebelumnya Anda perlu mengekspornya export -f <function>

CATATAN: export -fspesifik untuk bash

karena hanya shell yang dapat menjalankan fungsi shell :

find / -exec /bin/bash -c 'function "$1"' bash {} \;

EDIT: pada dasarnya skrip Anda harus menyerupai ini:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;
h3rrmiller
sumber
1
export -fadalah kesalahan sintaksis dalam ksh, dan mencetak definisi fungsi ke layar pada zsh. Dalam semua ksh, zshdan bash, itu tidak menyelesaikan masalah.
rahmu
1
find / -exec /bin/bash -c 'function' \;
h3rrmiller
Sekarang berfungsi, tetapi hanya dengan bash. Terima kasih!
rahmu
4
Jangan pernah menyematkan {}kode shell! Itu berarti nama file ditafsirkan sebagai kode shell sehingga sangat berbahaya
Stéphane Chazelas