Beberapa kali ketika saya membaca tentang pemrograman saya menemukan konsep "panggilan balik".
Lucunya, saya tidak pernah menemukan penjelasan yang bisa saya sebut "didaktik" atau "jelas" untuk istilah "fungsi panggilan balik" ini (hampir semua penjelasan yang saya baca tampak cukup berbeda dari yang lain dan saya merasa bingung).
Apakah konsep pemrograman "panggilan balik" ada di Bash? Jika demikian, harap jawab dengan contoh kecil, sederhana, Bash.
declarative.bash
menarik, sebagai kerangka kerja yang secara eksplisit memanfaatkan fungsi yang dikonfigurasikan untuk dipanggil ketika nilai yang diberikan diperlukan.Jawaban:
Dalam pemrograman imperatif tipikal , Anda menulis urutan instruksi dan mereka dieksekusi satu demi satu, dengan aliran kontrol eksplisit. Sebagai contoh:
dll.
Seperti yang dapat dilihat dari contoh, dalam pemrograman imperatif Anda mengikuti alur eksekusi dengan cukup mudah, selalu bekerja dengan cara Anda dari setiap baris kode tertentu untuk menentukan konteks eksekusi, mengetahui bahwa setiap instruksi yang Anda berikan akan dieksekusi sebagai hasil dari perintah mereka. lokasi di aliran (atau lokasi situs panggilan mereka, jika Anda menulis fungsi).
Bagaimana panggilan balik mengubah alur
Saat Anda menggunakan panggilan balik, alih-alih menempatkan penggunaan serangkaian instruksi "secara geografis", Anda menjelaskan kapan harus dipanggil. Contoh umum dalam lingkungan pemrograman lain adalah kasus seperti "unduh sumber ini, dan ketika unduhan selesai, panggil panggilan balik ini". Bash tidak memiliki konstruk panggilan balik generik dari jenis ini, tetapi ia memiliki panggilan balik, untuk penanganan kesalahan dan beberapa situasi lainnya; misalnya (kita harus terlebih dahulu memahami substitusi perintah dan mode keluar Bash untuk memahami contoh itu):
Jika Anda ingin mencoba ini sendiri, simpan di atas dalam file, katakan
cleanUpOnExit.sh
, buat itu dapat dieksekusi dan jalankan:Kode saya di sini tidak pernah secara eksplisit memanggil
cleanup
fungsi; ia memberi tahu Bash kapan harus memanggilnya, menggunakantrap cleanup EXIT
, yaitu "Bash sayang, tolong jalankancleanup
perintah ketika Anda keluar" (dancleanup
kebetulan merupakan fungsi yang saya definisikan sebelumnya, tapi itu bisa apa saja yang dipahami Bash). Bash mendukung ini untuk semua sinyal non-fatal, keluar, kegagalan perintah, dan debugging umum (Anda dapat menentukan panggilan balik yang dijalankan sebelum setiap perintah). Callback di sini adalahcleanup
fungsi, yang "dipanggil kembali" oleh Bash tepat sebelum shell keluar.Anda dapat menggunakan kemampuan Bash untuk mengevaluasi parameter shell sebagai perintah, untuk membangun kerangka kerja berorientasi-panggilan balik; itu agak di luar ruang lingkup jawaban ini, dan mungkin akan menyebabkan lebih banyak kebingungan dengan menyarankan bahwa fungsi lewat di sekitar selalu melibatkan panggilan balik. Lihat Bash: meneruskan fungsi sebagai parameter untuk beberapa contoh fungsionalitas yang mendasarinya. Idenya di sini, seperti halnya callback penanganan event, adalah bahwa fungsi dapat mengambil data sebagai parameter, tetapi juga fungsi lainnya - ini memungkinkan penelepon untuk memberikan perilaku serta data. Contoh sederhana dari pendekatan ini bisa terlihat
(Saya tahu ini agak tidak berguna karena
cp
dapat menangani banyak file, hanya untuk ilustrasi.)Di sini kita membuat fungsi,,
doonall
yang mengambil perintah lain, diberikan sebagai parameter, dan menerapkannya ke seluruh parameternya; maka kita menggunakannya untuk memanggilbackup
fungsi pada semua parameter yang diberikan ke skrip. Hasilnya adalah skrip yang menyalin semua argumennya, satu per satu, ke direktori cadangan.Pendekatan semacam ini memungkinkan fungsi ditulis dengan tanggung jawab tunggal:
doonall
tanggung jawab adalah menjalankan sesuatu pada semua argumennya, satu per satu;backup
Tanggung jawabnya adalah membuat salinan argumennya (satu-satunya) di direktori cadangan. Keduanyadoonall
danbackup
dapat digunakan dalam konteks lain, yang memungkinkan lebih banyak penggunaan kembali kode, pengujian yang lebih baik, dll.Dalam hal ini, callback adalah
backup
fungsi, yang kita beri tahudoonall
untuk “menelepon kembali” pada masing-masing argumen lainnya - kami menyediakandoonall
perilaku (argumen pertama) serta data (argumen yang tersisa).(Perhatikan bahwa dalam jenis use-case yang ditunjukkan pada contoh kedua, saya tidak akan menggunakan istilah "callback" sendiri, tapi itu mungkin kebiasaan yang dihasilkan dari bahasa yang saya gunakan. Saya menganggap ini sebagai fungsi lewat atau lambda di sekitar , daripada mendaftarkan panggilan balik dalam sistem berorientasi peristiwa.)
sumber
Pertama, penting untuk dicatat bahwa apa yang menjadikan suatu fungsi fungsi panggil balik adalah bagaimana fungsi itu digunakan, bukan apa fungsinya. Panggilan balik adalah ketika kode yang Anda tulis dipanggil dari kode yang tidak Anda tulis. Anda meminta sistem untuk menelepon Anda kembali ketika beberapa peristiwa tertentu terjadi.
Contoh panggilan balik dalam pemrograman shell adalah trap. Jebakan adalah panggilan balik yang tidak dinyatakan sebagai fungsi, tetapi sebagai bagian dari kode untuk dievaluasi. Anda meminta shell untuk memanggil kode Anda ketika shell menerima sinyal tertentu.
Contoh lain dari callback adalah
-exec
aksi darifind
perintah. Tugas darifind
perintah ini adalah untuk menelusuri direktori secara rekursif dan memproses setiap file secara bergantian. Secara default, pemrosesan adalah untuk mencetak nama file (implisit-print
), tetapi dengan-exec
pemrosesan adalah untuk menjalankan perintah yang Anda tentukan. Ini sesuai dengan definisi panggilan balik, meskipun saat panggilan balik berlangsung, itu tidak terlalu fleksibel karena panggilan balik berjalan dalam proses terpisah.Jika Anda menerapkan fungsi find-like, Anda bisa membuatnya menggunakan fungsi callback untuk memanggil setiap file. Inilah fungsi mirip-cari yang sangat disederhanakan yang menggunakan nama fungsi (atau nama perintah eksternal) sebagai argumen dan menyebutnya pada semua file biasa di direktori saat ini dan subdirektori. Fungsi ini digunakan sebagai panggilan balik yang dipanggil setiap kali
call_on_regular_files
menemukan file biasa.Callback tidak umum dalam pemrograman shell seperti di beberapa lingkungan lain karena shell terutama dirancang untuk program sederhana. Callback lebih umum di lingkungan di mana data dan aliran kontrol lebih cenderung bergerak bolak-balik antara bagian-bagian dari kode yang ditulis dan didistribusikan secara independen: sistem dasar, berbagai perpustakaan, kode aplikasi.
sumber
foreach_server() { declare callback="$1"; declare server; for server in 192.168.0.1 192.168.0.2 192.168.0.3; do "$callback" "$server"; done; }
mana Anda bisa menjalankan sebagaiforeach_server echo
,foreach_server nslookup
, dlldeclare callback="$1"
adalah tentang yang sederhana seperti itu bisa mendapatkan meskipun: callback telah akan disahkan di suatu tempat, atau itu bukan panggilan balik."panggilan balik" hanyalah fungsi yang diteruskan sebagai argumen ke fungsi lain.
Pada level shell, itu berarti skrip / fungsi / perintah dilewatkan sebagai argumen ke skrip / fungsi / perintah lain.
Sekarang, untuk contoh sederhana, pertimbangkan skrip berikut:
memiliki sinopsis
akan berlaku
filter
untuk setiapfile
argumen, lalu panggilcommand
dengan output filter sebagai argumen.Contohnya:
Ini sangat dekat dengan apa yang dapat Anda lakukan di lisp (hanya bercanda ;-))
Beberapa orang bersikeras membatasi istilah "callback" menjadi "event handler" dan / atau "closure" (fungsi + data / tuple lingkungan); ini sama sekali bukan makna yang diterima secara umum . Dan salah satu alasan mengapa "panggilan balik" dalam pengertian sempit itu tidak banyak digunakan dalam shell adalah karena pipa + paralelisme + kemampuan pemrograman dinamis jauh lebih kuat, dan Anda sudah membayar mereka dalam hal kinerja, bahkan jika Anda coba gunakan shell sebagai versi kikuk
perl
ataupython
.sumber
%
interpolasi di filter, semuanya dapat dikurangi ke:cmd=$1; shift; flt=$1; shift; $cmd <($flt "$1") <($flt "$2")
. Tapi itu jauh lebih berguna dan ilho ilustratif.$1 <($2 "$3") <($2 "$4")
Agak.
Salah satu cara sederhana untuk mengimplementasikan panggilan balik di bash, adalah dengan menerima nama program sebagai parameter, yang bertindak sebagai "fungsi panggilan balik".
Ini akan digunakan seperti ini:
Tentu saja Anda tidak memiliki penutupan di bash. Oleh karena itu, fungsi panggilan balik tidak memiliki akses ke variabel di sisi pemanggil. Namun, Anda dapat menyimpan data yang dibutuhkan panggilan balik dalam variabel lingkungan. Mengirim informasi kembali dari callback ke skrip invoker lebih sulit. Data dapat ditempatkan ke dalam file.
Jika desain Anda memungkinkan semuanya ditangani dalam satu proses tunggal, Anda dapat menggunakan fungsi shell untuk callback, dan dalam hal ini fungsi callback tentu saja memiliki akses ke variabel di sisi invoker.
sumber
Hanya dengan menambahkan beberapa kata ke jawaban lainnya. Fungsi panggil balik berfungsi pada fungsi eksternal fungsi yang memanggil kembali. Agar hal ini dimungkinkan, baik definisi keseluruhan dari fungsi yang dipanggil kembali perlu diteruskan ke fungsi yang memanggil kembali, atau kodenya harus tersedia untuk fungsi yang memanggil kembali.
Yang pertama (meneruskan kode ke fungsi lain) dimungkinkan, meskipun saya akan melewatkan contoh untuk ini akan melibatkan kompleksitas. Yang terakhir (melewati fungsi dengan nama) adalah praktik umum, karena variabel dan fungsi yang dideklarasikan di luar cakupan satu fungsi tersedia dalam fungsi tersebut selama definisi mereka mendahului panggilan ke fungsi yang beroperasi padanya (yang, pada gilirannya, , sebagaimana akan dinyatakan sebelum dipanggil).
Perhatikan juga, bahwa hal serupa terjadi ketika fungsi diekspor. Shell yang mengimpor fungsi mungkin memiliki kerangka kerja yang siap dan hanya menunggu definisi fungsi untuk menjalankannya. Fungsi ekspor hadir di Bash dan menyebabkan masalah yang sebelumnya serius, btw (yang disebut Shellshock):
Saya akan melengkapi jawaban ini dengan satu metode lagi untuk meneruskan fungsi ke fungsi lain, yang tidak secara eksplisit ada di Bash. Ini melewati alamat, bukan dengan nama. Ini dapat ditemukan di Perl, misalnya. Bash menawarkan cara ini baik untuk fungsi, maupun variabel. Tetapi jika, seperti yang Anda nyatakan, Anda ingin memiliki gambar yang lebih luas dengan Bash hanya sebagai contoh, maka Anda harus tahu, bahwa kode fungsi dapat berada di suatu tempat di memori, dan kode itu dapat diakses oleh lokasi memori itu, yang merupakan menyebut alamatnya.
sumber
Salah satu contoh paling sederhana dari callback di bash adalah salah satu yang banyak orang kenal tetapi tidak menyadari pola desain apa yang sebenarnya mereka gunakan:
cron
Cron memungkinkan Anda untuk menentukan yang dapat dieksekusi (biner atau skrip) yang akan dipanggil kembali oleh program cron ketika beberapa kondisi terpenuhi (spesifikasi waktu)
Katakanlah Anda memiliki skrip yang dipanggil
doEveryDay.sh
. Cara non-callback untuk menulis skrip adalah:Cara panggilan balik untuk menulisnya adalah:
Kemudian di crontab Anda akan mengatur sesuatu seperti
Maka Anda tidak perlu menulis kode untuk menunggu acara untuk memicu tetapi sebaliknya mengandalkan
cron
untuk memanggil kode Anda kembali.Sekarang, pertimbangkan BAGAIMANA Anda akan menulis kode ini di bash.
Bagaimana Anda menjalankan skrip / fungsi lain di bash?
Mari kita menulis fungsi:
Sekarang Anda telah membuat fungsi yang menerima panggilan balik. Anda bisa menyebutnya seperti ini:
Tentu saja, fungsi setiap 24 jam tidak pernah kembali. Bash agak unik karena kita dapat dengan mudah membuatnya asinkron dan menelurkan proses dengan menambahkan
&
:Jika Anda tidak menginginkan ini sebagai fungsi, Anda dapat melakukannya sebagai skrip:
Seperti yang Anda lihat, panggilan balik dalam bash adalah sepele. Ini hanyalah:
Dan menelepon panggilan balik itu sederhana:
Seperti yang Anda lihat pada formulir di atas, panggilan balik jarang merupakan fitur langsung dari bahasa. Mereka biasanya pemrograman secara kreatif menggunakan fitur bahasa yang ada. Bahasa apa pun yang dapat menyimpan pointer / referensi / salinan beberapa blok kode / fungsi / skrip dapat menerapkan panggilan balik.
sumber
watch
danfind
(bila digunakan dengan-exec
parameter)Panggilan balik adalah fungsi yang dipanggil saat beberapa peristiwa terjadi. Dengan
bash
, satu-satunya mekanisme penanganan peristiwa di tempat terkait dengan sinyal, keluar shell, dan diperluas ke acara kesalahan shell, acara debug dan fungsi / sumber acara kembali skrip bersumber.Berikut adalah contoh perangkap sinyal pengungkit balik yang tidak berguna tetapi sederhana.
Pertama buat skrip yang mengimplementasikan callback:
Kemudian jalankan skrip dalam satu terminal:
$ ./callback-example
dan pada yang lain, kirim
USR1
sinyal ke proses shell.Setiap sinyal yang dikirim harus memicu tampilan garis seperti ini di terminal pertama:
ksh93
, karena shell mengimplementasikan banyak fitur yangbash
kemudian diadopsi, menyediakan apa yang disebutnya "fungsi disiplin". Fungsi-fungsi ini, tidak tersedia denganbash
, dipanggil ketika variabel shell dimodifikasi atau direferensikan (yaitu dibaca). Ini membuka jalan ke aplikasi event driven yang lebih menarik.Misalnya, fitur ini memungkinkan panggilan balik gaya X11 / Xt / Motif pada widget grafis untuk diimplementasikan dalam versi lama dari
ksh
itu termasuk ekstensi grafik yang disebutdtksh
. Lihat manual dksh .sumber