Di tempat kerja, saya sering menulis skrip bash. Atasan saya menyarankan agar seluruh skrip dipecah menjadi fungsi, mirip dengan contoh berikut:
#!/bin/bash
# Configure variables
declare_variables() {
noun=geese
count=three
}
# Announce something
i_am_foo() {
echo "I am foo"
sleep 0.5
echo "hear me roar!"
}
# Tell a joke
walk_into_bar() {
echo "So these ${count} ${noun} walk into a bar..."
}
# Emulate a pendulum clock for a bit
do_baz() {
for i in {1..6}; do
expr $i % 2 >/dev/null && echo "tick" || echo "tock"
sleep 1
done
}
# Establish run order
main() {
declare_variables
i_am_foo
walk_into_bar
do_baz
}
main
Apakah ada alasan untuk melakukan hal ini selain "keterbacaan", yang saya pikir dapat juga dilakukan dengan beberapa komentar dan spasi baris?
Apakah ini membuat script berjalan lebih efisien (saya benar-benar mengharapkan yang sebaliknya, jika ada), atau apakah itu membuatnya lebih mudah untuk memodifikasi kode di luar potensi keterbacaan yang disebutkan sebelumnya? Atau itu benar-benar hanya preferensi gaya?
Harap perhatikan bahwa meskipun skrip tidak menunjukkannya dengan baik, "menjalankan urutan" fungsi dalam skrip aktual kami cenderung sangat linier - walk_into_bar
tergantung pada hal-hal yang i_am_foo
telah dilakukan, dan do_baz
tindakan pada hal-hal yang diatur oleh walk_into_bar
- jadi dapat secara sewenang-wenang menukar run order bukanlah sesuatu yang biasanya kita lakukan. Misalnya, Anda tidak akan tiba-tiba ingin declare_variables
mengejarnya walk_into_bar
, itu akan menghancurkan banyak hal.
Contoh bagaimana saya akan menulis skrip di atas adalah:
#!/bin/bash
# Configure variables
noun=geese
count=three
# Announce something
echo "I am foo"
sleep 0.5
echo "hear me roar!"
# Tell a joke
echo "So these ${count} ${noun} walk into a bar..."
# Emulate a pendulum clock for a bit
for i in {1..6}; do
expr $i % 2 >/dev/null && echo "tick" || echo "tock"
sleep 1
done
sumber
main()
di bagian atas dan menambahkanmain "$@"
di bawah untuk menyebutnya. Itu memungkinkan Anda melihat logika skrip tingkat tinggi hal pertama ketika Anda membukanya.local
- ini menyediakan ruang lingkup variabel yang sangat penting dalam skrip non-sepele.Jawaban:
Saya sudah mulai menggunakan gaya pemrograman bash yang sama ini setelah membaca posting blog Kfir Lavi "Defensive Bash Programming" . Dia memberikan beberapa alasan bagus, tetapi secara pribadi saya menemukan ini yang paling penting:
prosedur menjadi deskriptif: jauh lebih mudah untuk mengetahui bagian kode apa yang seharusnya dilakukan. Alih-alih dinding kode, Anda melihat "Oh,
find_log_errors
fungsi membaca file log untuk kesalahan". Bandingkan dengan menemukan banyak baris awk / grep / sed yang menggunakan tuhan tahu apa jenis regex di tengah naskah panjang - Anda tidak tahu apa yang dilakukannya di sana kecuali ada komentar.Anda dapat men-debug fungsi dengan menyertakan ke
set -x
danset +x
. Setelah Anda mengetahui bahwa sisa kode berfungsi dengan baik, Anda dapat menggunakan trik ini untuk fokus pada debugging hanya fungsi spesifik itu. Tentu, Anda dapat melampirkan bagian skrip, tetapi bagaimana jika itu bagian yang panjang? Lebih mudah melakukan hal seperti ini:penggunaan pencetakan dengan
cat <<- EOF . . . EOF
. Saya telah menggunakannya beberapa kali untuk membuat kode saya jauh lebih profesional. Selain itu,parse_args()
dengangetopts
fungsinya yang cukup nyaman. Sekali lagi, ini membantu keterbacaan, alih-alih memasukkan semuanya ke dalam skrip sebagai dinding teks raksasa. Juga nyaman untuk menggunakan kembali ini.Dan jelas, ini jauh lebih mudah dibaca untuk seseorang yang tahu C atau Java, atau Vala, tetapi memiliki pengalaman bash yang terbatas. Sejauh efisiensi berjalan, tidak ada banyak yang dapat Anda lakukan - bash itu sendiri bukan bahasa yang paling efisien dan orang lebih suka perl dan python ketika datang ke kecepatan dan efisiensi. Namun, Anda dapat
nice
suatu fungsi:Dibandingkan dengan memanggil baik pada setiap baris kode, ini mengurangi banyak mengetik DAN dapat dengan mudah digunakan ketika Anda ingin hanya bagian dari skrip Anda berjalan dengan prioritas lebih rendah.
Menjalankan fungsi di latar belakang, menurut saya, juga membantu ketika Anda ingin memiliki banyak pernyataan untuk dijalankan di latar belakang.
Beberapa contoh di mana saya menggunakan gaya ini:
sumber
local
dan memanggil semuanya melaluimain()
fungsi. Ini membuat banyak hal lebih mudah dikelola dan Anda dapat menghindari situasi yang berpotensi berantakan.Keterbacaan adalah satu hal. Tetapi ada lebih banyak modularisasi daripada hanya ini. ( Semi-modularisasi mungkin lebih tepat untuk fungsi.)
Dalam fungsi Anda dapat menyimpan beberapa variabel lokal, yang meningkatkan keandalan , mengurangi kemungkinan hal-hal kacau.
Pro fungsi lainnya adalah kegunaan ulang . Setelah suatu fungsi dikodekan, itu dapat diterapkan beberapa kali dalam skrip. Anda juga dapat memindahkannya ke skrip lain.
Kode Anda sekarang mungkin linier, tetapi di masa depan Anda dapat memasukkan ranah multi-threading , atau multi-pemrosesan di dunia Bash. Setelah Anda belajar melakukan hal-hal dalam fungsi, Anda akan diperlengkapi dengan baik untuk melangkah ke paralel.
Satu hal lagi untuk ditambahkan. Sebagaimana Etsitpab Nioliv perhatikan dalam komentar di bawah, mudah untuk mengalihkan dari fungsi sebagai entitas yang koheren. Tetapi ada satu aspek lagi dari pengalihan dengan fungsi. Yaitu, pengalihan dapat diatur di sepanjang definisi fungsi. Misalnya.:
Sekarang tidak diperlukan pengalihan secara eksplisit oleh pemanggilan fungsi.
Ini dapat menghemat banyak pengulangan, yang lagi-lagi meningkatkan keandalan dan membantu menjaga barang-barang tetap teratur.
Lihat juga
sumber
source
atau. scriptname.sh
, dan menggunakan fungsi-fungsi itu seolah-olah mereka ada di skrip baru Anda.Dalam komentar saya, saya menyebutkan tiga keunggulan fungsi:
Mereka lebih mudah untuk menguji dan memverifikasi kebenaran.
Fungsi dapat dengan mudah digunakan kembali (bersumber) dalam skrip yang akan datang
Bos Anda menyukai mereka.
Dan, jangan pernah meremehkan pentingnya angka 3.
Saya ingin membahas satu masalah lagi:
Untuk mendapatkan manfaat dari memecah kode menjadi fungsi, seseorang harus mencoba untuk membuat fungsi sebebas mungkin. Jika
walk_into_bar
memerlukan variabel yang tidak digunakan di tempat lain, maka variabel tersebut harus didefinisikan dan dibuat lokal untukwalk_into_bar
. Proses memisahkan kode menjadi fungsi dan meminimalkan saling ketergantungan mereka harus membuat kode lebih jelas dan sederhana.Idealnya, fungsi harus mudah diuji secara individual. Jika, karena interaksi, mereka tidak mudah diuji, maka itu adalah tanda bahwa mereka mungkin mendapat manfaat dari refactoring.
sumber
;-)
Anda memecah kode menjadi fungsi untuk alasan yang sama Anda akan melakukannya untuk C / C ++, python, perl, ruby atau kode bahasa pemrograman apa pun. Alasan yang lebih dalam adalah abstraksi - Anda merangkum tugas-tugas tingkat bawah menjadi primitif tingkat tinggi (fungsi) sehingga Anda tidak perlu repot tentang bagaimana hal-hal dilakukan. Pada saat yang sama, kode menjadi lebih mudah dibaca (dan dipelihara), dan logika program menjadi lebih jelas.
Namun, melihat kode Anda, saya merasa sangat aneh memiliki fungsi untuk mendeklarasikan variabel; ini benar-benar membuat saya menaikkan alis mata.
sumber
main
fungsi / metode, lalu?Sementara saya sepenuhnya setuju dengan usabilitas , keterbacaan , dan mencium bos dengan lembut tetapi ada satu keuntungan lain dari fungsi di bash : ruang lingkup variabel . Seperti yang ditunjukkan LDP :
Saya tidak sering melihat ini di skrip shell dunia nyata, tetapi sepertinya ide yang bagus untuk skrip yang lebih kompleks. Mengurangi kohesi membantu menghindari bug di mana Anda menghancurkan variabel yang diharapkan di bagian lain kode.
Dapat digunakan kembali sering kali berarti membuat perpustakaan umum fungsi dan
source
memasukkan perpustakaan itu ke semua skrip Anda. Ini tidak akan membantu mereka berlari lebih cepat, tetapi itu akan membantu Anda menulisnya lebih cepat.sumber
local
, tetapi saya pikir sebagian besar orang menulis skrip dipecah menjadi fungsi masih mengikuti prinsip desain. Usignlocal
hanya mempersulit untuk memperkenalkan bug.local
membuat variabel tersedia untuk fungsi dan anak-anaknya, jadi sangat menyenangkan memiliki variabel yang dapat diturunkan dari fungsi A, tetapi tidak tersedia untuk fungsi B, yang mungkin ingin memiliki variabel dengan nama yang sama tetapi tujuan yang berbeda. Jadi itu bagus untuk mendefinisikan ruang lingkup, dan seperti yang dikatakan Voo - lebih sedikit bugAlasan yang sangat berbeda dari yang sudah diberikan dalam jawaban lain: satu alasan teknik ini kadang-kadang digunakan, di mana satu-satunya pernyataan non-fungsi-definisi di tingkat atas adalah panggilan untuk
main
, adalah untuk memastikan skrip tidak secara tidak sengaja melakukan sesuatu yang jahat jika skrip terpotong. Script dapat dipotong jika disalurkan dari proses A ke proses B (shell), dan proses A berakhir karena alasan apa pun sebelum selesai menulis keseluruhan skrip. Ini sangat mungkin terjadi jika proses A mengambil skrip dari sumber daya jarak jauh. Sementara untuk alasan keamanan itu bukan ide yang baik, itu adalah sesuatu yang dilakukan, dan beberapa skrip telah dimodifikasi untuk mengantisipasi masalah.sumber
main()
pola ini persis biasa di Python di mana orang menggunakanif __name__ == '__main__': main()
di akhir file.import
saat ini tanpa berjalanmain
. Saya kira penjaga yang sama bisa dimasukkan ke dalam skrip bash.Suatu proses membutuhkan urutan. Sebagian besar tugas berurutan. Tidak masuk akal untuk mengacaukan pesanan.
Tetapi hal super besar tentang pemrograman - yang termasuk scripting - adalah pengujian. Pengujian, pengujian, pengujian. Skrip tes apa yang saat ini Anda miliki untuk memvalidasi kebenaran skrip Anda?
Bos Anda mencoba memandu Anda dari menjadi script kiddy menjadi programmer. Ini adalah arah yang baik untuk masuk. Orang-orang yang datang setelah Anda akan menyukai Anda.
TAPI. Ingatlah selalu akar proses Anda. Jika masuk akal untuk memiliki fungsi yang dipesan dalam urutan di mana mereka biasanya dieksekusi, maka lakukan itu, setidaknya sebagai pass pertama.
Kemudian, Anda akan melihat bahwa beberapa fungsi Anda menangani input, yang lain output, yang lain memproses, yang lain memodelkan data, dan yang lain memanipulasi data, sehingga mungkin pintar untuk mengelompokkan metode yang serupa, mungkin bahkan memindahkannya ke file yang terpisah. .
Kemudian, Anda mungkin menyadari bahwa sekarang Anda telah menulis perpustakaan fungsi pembantu kecil yang Anda gunakan di banyak skrip Anda.
sumber
Komentar dan spasi tidak dapat mendekati keterbacaan yang berfungsi, seperti yang akan saya tunjukkan. Tanpa fungsi, Anda tidak dapat melihat hutan untuk pepohonan - masalah besar bersembunyi di antara banyak garis detail. Dengan kata lain, orang tidak bisa secara bersamaan fokus pada detail halus dan gambaran besar. Itu mungkin tidak jelas dalam naskah pendek; selama masih pendek mungkin cukup mudah dibaca. Namun, perangkat lunak menjadi lebih besar, tidak lebih kecil, dan tentu saja itu bagian dari keseluruhan sistem perangkat lunak perusahaan Anda, yang tentunya jauh lebih besar, mungkin jutaan baris.
Pertimbangkan jika saya memberi Anda petunjuk seperti ini:
Pada saat Anda setengah jalan, atau bahkan 5% melalui, Anda akan lupa apa beberapa langkah pertama itu. Anda tidak mungkin menemukan sebagian besar masalah, karena Anda tidak dapat melihat hutan untuk pepohonan. Bandingkan dengan fungsi:
Itu tentu jauh lebih dimengerti, tidak peduli berapa banyak komentar yang Anda masukkan ke dalam versi berurutan baris demi baris. Ini juga membuatnya jauh lebih mungkin Anda akan melihat bahwa Anda lupa membuat kopi, dan mungkin lupa sit_down () di akhir. Ketika pikiran Anda memikirkan perincian grep dan awk regexes, Anda tidak dapat memikirkan gambaran besar - "bagaimana jika tidak ada kopi yang dibuat"?
Fungsi utamanya memungkinkan Anda melihat gambaran besar, dan memerhatikan bahwa Anda lupa membuat kopi (atau seseorang mungkin lebih suka teh). Di lain waktu, dalam kerangka berpikir yang berbeda, Anda khawatir tentang implementasi terperinci.
Ada juga manfaat lain yang dibahas dalam jawaban lain, tentu saja. Manfaat lain yang tidak secara jelas dinyatakan dalam jawaban lain adalah bahwa fungsi memberikan jaminan yang penting dalam mencegah dan memperbaiki bug. Jika Anda menemukan bahwa beberapa variabel $ foo dalam fungsi walk_to () yang salah salah, Anda tahu bahwa Anda hanya perlu melihat 6 baris lainnya dari fungsi itu untuk menemukan segala sesuatu yang dapat dipengaruhi oleh masalah itu, dan segala sesuatu yang bisa telah menyebabkannya salah. Tanpa fungsi (tepat), apa pun dan segala sesuatu di seluruh sistem mungkin menjadi penyebab $ foo salah, dan apa pun dan segala sesuatu mungkin dipengaruhi oleh $ foo. Karenanya Anda tidak dapat memperbaiki $ foo dengan aman tanpa memeriksa ulang setiap baris program. Jika $ foo bersifat lokal untuk suatu fungsi,
sumber
bash
sintaksis. Sayang sekali; Saya tidak berpikir ada cara untuk memasukkan input ke fungsi seperti itu. (yaitupour();
<coffee
). Itu lebih miripc++
atauphp
(saya pikir).Beberapa kebenaran yang relevan tentang pemrograman:
Komentar dimulai sebagai penghenti karena tidak dapat mengekspresikan ide Anda dengan jelas dalam kode *, dan menjadi lebih buruk (atau hanya salah) dengan perubahan. Karena itu, jika memungkinkan, ungkapkan konsep, struktur, alasan, semantik, aliran, penanganan kesalahan, dan hal lain yang berkaitan dengan pemahaman kode sebagai kode.
Yang mengatakan, fungsi Bash memiliki beberapa masalah yang tidak ditemukan di sebagian besar bahasa:
local
hasil kata kunci dalam mencemari namespace global.local foo="$(bar)"
hasil dalam kehilangan kode keluar daribar
."$@"
artinya dalam konteks yang berbeda.* Maaf jika ini menyinggung, tetapi setelah menggunakan komentar selama beberapa tahun dan berkembang tanpa mereka ** selama bertahun-tahun cukup jelas mana yang lebih unggul.
** Menggunakan komentar untuk lisensi, dokumentasi API dan sejenisnya masih diperlukan.
sumber
local foo=""
Kemudian pengaturan mereka menggunakan perintah eksekusi untuk bertindak atas hasilnya ...foo="$(bar)" || { echo "bar() failed"; return 1; }
. Ini membuat kita keluar dari fungsi dengan cepat ketika nilai yang diperlukan tidak dapat ditetapkan. Kurung kurawal diperlukan untuk memastikan bahwareturn 1
hanya dijalankan pada kegagalan.Waktu adalah uang
Ada jawaban bagus lainnya yang menyebarkan alasan teknis untuk menulis skrip modular , berpotensi panjang, dikembangkan di lingkungan kerja, dikembangkan untuk digunakan oleh sekelompok orang dan tidak hanya untuk penggunaan Anda sendiri.
Saya ingin fokus pada satu harapan: dalam lingkungan kerja "waktu adalah uang" . Jadi tidak adanya bug dan kinerja kode Anda dievaluasi bersama dengan readibility , testability, rawatan, refactorability, reusability ...
Menulis dalam "modul" kode akan mengurangi waktu membaca yang dibutuhkan tidak hanya oleh koder itu sendiri, tetapi bahkan waktu yang digunakan oleh penguji atau oleh bos. Selain itu perhatikan bahwa waktu seorang bos biasanya dibayar lebih dari waktu seorang pembuat kode dan bahwa bos Anda akan mengevaluasi kualitas pekerjaan Anda.
Selanjutnya menulis dalam "modul" kode independen (bahkan skrip bash) akan memungkinkan Anda untuk bekerja secara "paralel" dengan komponen lain dari tim Anda yang memperpendek waktu produksi keseluruhan dan menggunakan keahlian terbaik single, untuk meninjau atau menulis ulang bagian dengan tidak ada efek samping pada yang lain, untuk mendaur ulang kode yang baru saja Anda tulis "apa adanya"untuk program / skrip lain, untuk membuat pustaka (atau pustaka snippet), untuk mengurangi ukuran keseluruhan dan kemungkinan kesalahan yang terkait, untuk men-debug dan menguji secara menyeluruh setiap bagian tunggal ... dan tentu saja akan diatur dalam bagian logis program Anda / skrip dan tingkatkan keterbacaannya. Semua hal yang akan menghemat waktu dan uang. Kekurangannya adalah Anda harus tetap berpegang pada standar dan mengomentari fungsi Anda (yang harus Anda lakukan di lingkungan kerja).
Mematuhi standar akan memperlambat pekerjaan Anda di awal tetapi akan mempercepat pekerjaan semua yang lain (dan Anda juga) sesudahnya. Memang ketika kolaborasi tumbuh dalam jumlah orang yang terlibat ini menjadi kebutuhan yang tidak dapat dihindari. Jadi, misalnya, bahkan jika saya percaya bahwa variabel global harus didefinisikan secara global dan tidak dalam suatu fungsi, saya dapat memahami suatu standar yang mem-inisialisasi mereka dalam suatu fungsi yang dinamakan
declare_variables()
selalu di baris pertamamain()
...Last but not least, jangan meremehkan kemungkinan dalam editor kode sumber modern untuk menunjukkan atau menyembunyikan rutinitas terpisah secara selektif ( Lipat kode ). Ini akan menjaga kode kompak dan memfokuskan pengguna menghemat waktu lagi.
Di sini di atas Anda dapat melihat bagaimana itu hanya membuka
walk_into_bar()
fungsi. Bahkan yang lainnya masing-masing terdiri dari 1000 baris, Anda masih dapat mengontrol semua kode dalam satu halaman. Catatan itu dilipat bahkan bagian mana Anda pergi untuk mendeklarasikan / menginisialisasi variabel.sumber
Selain alasan yang diberikan dalam jawaban lain:
sumber
Alasan lain yang sering diabaikan adalah sintaks bash:
Script ini jelas mengandung kesalahan sintaks dan bash seharusnya tidak menjalankannya sama sekali, kan? Salah.
Jika kami membungkus kode dalam suatu fungsi, ini tidak akan terjadi:
sumber