Dalam artikel ini, saya menyajikan beberapa trik untuk menangani kondisi kesalahan—Beberapa benar-benar tidak termasuk dalam kategori penanganan kesalahan (cara reaktif untuk menangani hal yang tidak terduga) tetapi juga beberapa teknik untuk menghindari kesalahan sebelum terjadi.
Studi kasus:Skrip sederhana yang mengunduh laporan perangkat keras dari beberapa host dan memasukkannya ke dalam database.
Katakanlah Anda memiliki cron
pekerjaan di setiap sistem Linux Anda, dan Anda memiliki skrip untuk mengumpulkan informasi perangkat keras dari masing-masing sistem:
#!/bin/bash# Skrip untuk mengumpulkan status keluaran lshw dari server rumah# Ketergantungan:# * LSHW:http://ezix.org/project/wiki/HardwareLiSter# * JQ:http://stedolan.github.io/jq/## Di setiap mesin Anda dapat menjalankan sesuatu seperti ini dari cron (Tidak tahu CRON, jangan khawatir:https://crontab-generator.org/)# 0 0 * * * /usr/sbin/lshw -json -quiet> /var/log/lshw-dump.json# Penulis:Jose Vicente Nunez#declare -a server=(dmaf5)DATADIR="$HOME/Documents/lshw-dump"/usr /bin/mkdir -p -v "$DATADIR"untuk server di ${servers[*]}; do echo "Mengunjungi:$server" /usr/bin/scp -o logLevel=Error ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json &donewaitfor lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json'); lakukan /usr/bin/jq '.["product","vendor", "configuration"]' $lshwdone
Jika semuanya berjalan dengan baik, Anda mengumpulkan file secara paralel karena Anda tidak memiliki lebih dari sepuluh sistem. Anda dapat melakukan ssh ke semuanya secara bersamaan dan kemudian menampilkan detail perangkat keras masing-masing.
Kunjungan:DMAF5LSHW-DUMP.json 100% 54KB 136.9MB/S 00:00 "DMAF5 (String Default)" "Besstar Tech Limited" {"Boot":"Normal", "Chassis":"Desktop ", "family":"String default", "sku":"String default", "uuid":"00020003-0004-0005-0006-000700080009"}
Berikut adalah beberapa kemungkinan mengapa terjadi kesalahan:
- Laporan Anda tidak berjalan karena server sedang down
- Anda tidak dapat membuat direktori tempat file harus disimpan
- Alat yang Anda perlukan untuk menjalankan skrip tidak ada
- Anda tidak dapat mengumpulkan laporan karena mesin jarak jauh Anda mogok
- Satu atau beberapa laporan rusak
Versi skrip saat ini bermasalah—Ini akan berjalan dari awal hingga akhir, error atau tidak:
./ collect_data_from_servers.sh Kunjungan:macMini2visiting:mac-pro-1-visiting:dmaf5lshw-dump.json 100% 54kb 48.8mb/s 00:00 scp:/var/log/lshw-dump.json :Tidak ada file atau direktori seperti ituscp:/var/log/lshw-dump.json:Tidak ada kesalahan file atau directoryparse tersebut:Pemisah yang diharapkan antara nilai pada baris 3, kolom 9
Selanjutnya, saya mendemonstrasikan beberapa hal untuk membuat skrip Anda lebih kuat dan terkadang pulih dari kegagalan.
Opsi nuklir:Gagal keras, gagal cepat
Cara yang tepat untuk menangani kesalahan adalah untuk memeriksa apakah program selesai dengan sukses atau tidak, menggunakan kode kembali. Kedengarannya jelas tetapi mengembalikan kode, nomor integer yang disimpan di bash $?
atau $!
variabel, terkadang memiliki arti yang lebih luas. Halaman manual bash memberi tahu Anda:
Untuk tujuan shell, perintah yang keluar dengan status
keluar nol telah berhasil. Status keluar nol menunjukkan keberhasilan.
Status keluar bukan nol menunjukkan kegagalan. Saat perintah
berakhir pada sinyal fatal N, bash menggunakan nilai 128+N sebagai
status keluar.
Seperti biasa, Anda harus selalu membaca halaman manual dari skrip yang Anda panggil, untuk melihat apa konvensi untuk masing-masing skrip. Jika Anda telah memprogram dengan bahasa seperti Java atau Python, kemungkinan besar Anda sudah familiar dengan pengecualian mereka, arti yang berbeda, dan bagaimana tidak semuanya ditangani dengan cara yang sama.
Jika Anda menambahkan set -o errexit
ke skrip Anda, sejak saat itu akan membatalkan eksekusi jika ada perintah dengan kode !=0
. Tapi errexit
tidak digunakan saat menjalankan fungsi di dalam if
kondisi, jadi daripada mengingat pengecualian itu, saya lebih suka melakukan penanganan kesalahan eksplisit.
Lihat skrip versi kedua. Ini sedikit lebih baik:
1 #!/bin/bash2 # Skrip untuk mengumpulkan status keluaran lshw dari server rumah3 # Dependensi:4 # * LSHW:http://ezix.org/project/wiki/HardwareLiSter5 # * JQ:http://stedolan.github.io/jq/6 #7 # Di setiap mesin Anda dapat menjalankan sesuatu seperti ini dari cron (Tidak tahu CRON, jangan khawatir:https://crontab-generator.org/ ) 8 # 0 0 * * * /usr/sbin/lshw -json -quiet> /var/log/lshw-dump.json9 Penulis:Jose Vicente Nunez10 #11 set -o errtrace # Aktifkan jebakan err, kode akan dipanggil saat terjadi kesalahan terdeteksi12 jebakan "echo ERROR:Ada kesalahan dalam ${FUNCNAME-main context}, detail mengikuti" ERR13 mendeklarasikan -a server=(14 macmini215 mac-pro-1-116 dmaf517 )18 19 DATADIR="$HOME/ Documents/lshw-dump"20 jika [ ! -d "$DATADIR" ]; lalu 21 /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL:Gagal membuat $DATADIR" &&exit 10022 fi 23 mendeklarasikan -A server_pid24 untuk server di ${servers[*]}; do25 echo "Mengunjungi:$server"26 /usr/bin/scp -o logLevel=Error ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json &27 server_pid [$server]=$! # Simpan PID dari scp dari server yang diberikan untuk nanti28 done29 # Iterate melalui semua server dan:30 # Tunggu kode kembalian masing-masing31 # Periksa kode keluar dari setiap scp32 untuk server di ${!server_pid[*]}; do33 tunggu ${server_pid[$server]}34 uji $? -ne 0 &&echo "ERROR:Salin dari $server bermasalah, tidak akan dilanjutkan" &&keluar 10035 done36 untuk lshw di $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json' ); do37 /usr/bin/jq '.["product","vendor", "configuration"]' $lshw38 selesai
Inilah yang berubah:
- Baris 11 dan 12, saya mengaktifkan pelacakan kesalahan dan menambahkan 'jebakan' untuk memberi tahu pengguna bahwa ada kesalahan dan ada turbulensi di depan. Anda mungkin ingin mematikan skrip Anda di sini, saya akan menunjukkan kepada Anda mengapa itu mungkin bukan yang terbaik.
- Baris 20, jika direktori tidak ada, coba buat di baris 21. Jika pembuatan direktori gagal, keluar dengan kesalahan.
- Pada baris 27, setelah menjalankan setiap pekerjaan latar belakang, saya menangkap PID dan mengaitkannya dengan mesin (hubungan (1:1).
- Pada baris 33-35, saya menunggu
scp
tugas untuk diselesaikan, dapatkan kode pengembalian, dan jika itu kesalahan, batalkan. - Pada baris 37, saya memeriksa apakah file dapat diuraikan, jika tidak, saya keluar dengan kesalahan.
Jadi bagaimana penanganan kesalahan sekarang?
Kunjungan:MacMini2Visiting:mac-pro-1-visiting:dmaf5lshw-dump.json 100% 54kb 146.1mb/s 00:00 scp:/var/log/lshw-dump.json:tidak ada file tersebut atau directoryERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiERROR:Salin dari mac-pro-1-1 mengalami masalah, tidak akan berlanjutcp:/var/log/lshw-dump.json:Tidak ada file atau direktori seperti itu
Seperti yang Anda lihat, versi ini lebih baik dalam mendeteksi kesalahan tetapi sangat tak kenal ampun. Juga, itu tidak mendeteksi semua kesalahan, bukan?
Saat Anda buntu dan ingin memiliki alarm
Kode terlihat lebih baik, kecuali terkadang scp
bisa macet di server (saat mencoba menyalin file) karena server terlalu sibuk untuk merespons atau hanya dalam kondisi buruk.
Contoh lain adalah mencoba mengakses direktori melalui NFS di mana $HOME
dipasang dari server NFS:
/usr/bin/find $HOME -type f -name '*.csv' -print -fprint /tmp/report.txt
Dan Anda menemukan beberapa jam kemudian bahwa titik pemasangan NFS sudah kedaluwarsa dan skrip Anda macet.
Batas waktu adalah solusinya. Dan, batas waktu GNU datang untuk menyelamatkan:
/usr/bin/timeout --kill-after 20.0s 10.0s /usr/bin/find $HOME -type f -name '*.csv' -print -fprint /tmp/report.txt
Di sini Anda mencoba untuk secara teratur membunuh (sinyal TERM) proses dengan baik setelah 10,0 detik setelah dimulai. Jika masih berjalan setelah 20,0 detik, maka kirim sinyal KILL (kill -9
). Jika ragu, periksa sinyal mana yang didukung di sistem Anda (kill -l
, misalnya).
Jika ini tidak jelas dari dialog saya, lihat skrip untuk kejelasan lebih lanjut.
/usr/bin/time /usr/bin/timeout --kill-after=10.0s 20.0s /usr/bin/sleep 60sreal 0m20.003suser 0m0.000ssys 0m0.003s
Kembali ke skrip asli untuk menambahkan beberapa opsi lagi dan Anda memiliki versi tiga:
1 #!/bin/bash 2 # Script untuk mengumpulkan status keluaran lshw dari server rumah 3 # Dependensi:4 # * Buka SSH:http://www.openssh.com/portable.html 5 # * LSHW:http://ezix.org/project/wiki/HardwareLiSter 6 # * JQ:http://stedolan.github.io/jq/ 7 # * batas waktu:https://www.gnu.org/software /coreutils/ 8 #9 # Di setiap mesin Anda dapat menjalankan sesuatu seperti ini dari cron (Tidak tahu CRON, jangan khawatir:https://crontab-generator.org/) 10 # 0 0 * * * /usr/sbin /lshw -json -quiet> /var/log/lshw-dump.json 11 # Penulis:Jose Vicente Nunez 12 # 13 set -o errtrace # Aktifkan jebakan err, kode akan dipanggil ketika kesalahan terdeteksi 14 jebakan "echo KESALAHAN:Ada kesalahan dalam ${FUNCNAME-main context}, detail mengikuti" ERR 15 16 mendeklarasikan -a dependencies=(/usr/bin/timeout /usr/bin/ssh /usr/bin/jq) 17 untuk ketergantungan di ${dependensi[@]}; lakukan 18 jika [ ! -x $ketergantungan ]; kemudian 19 echo "ERROR:Missing $dependency" 20 exit 100 21 fi 22 selesai 23 24 mendeklarasikan -a server=( 25 macmini2 26 mac-pro-1-1 27 dmaf5 28 ) 29 30 function remote_copy { 31 server lokal=$1 32 echo "Visiting:$server" 33 /usr/bin/timeout --kill-after 25.0s 20.0s \ 34 /usr/bin/scp \ 35 -o BatchMode=yes \ 36 -o log \ Level=Error =5 \ 38 -o ConnectionAttempts=3 \ 39 ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json 40 return $? 41 } 42 43 DATADIR="$HOME/Documents/lshw-dump" 44 jika [ ! -d "$DATADIR" ]; lalu 45 /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL:Gagal membuat $DATADIR" &&exit 100 46 fi 47 mendeklarasikan -A server_pid 48 untuk server di ${servers[*]}; lakukan 49 remote_copy $server &50 server_pid[$server]=$! # Simpan PID scp dari server yang diberikan untuk nanti 51 selesai 52 # Ulangi semua server dan:53 # Tunggu kode pengembalian masing-masing 54 # Periksa kode keluar dari setiap scp 55 untuk server di ${!server_pid [*]}; apakah 56 tunggu ${server_pid[$server]} 57 uji $? -ne 0 &&echo "ERROR:Salin dari $server bermasalah, tidak akan dilanjutkan" &&exit 100 58 done 59 for lshw di $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump. json'); lakukan 60 /usr/bin/jq '.["product","vendor", "configuration"]' $lshw 61 selesai
Apa saja perubahannya?:
- Antara baris 16-22, periksa apakah semua alat ketergantungan yang diperlukan ada. Jika tidak bisa dijalankan, maka 'Houston kita punya masalah.'
- Membuat
remote_copy
fungsi, yang menggunakan batas waktu untuk memastikanscp
selesai paling lambat 45.0 detik—baris 33. - Menambahkan batas waktu koneksi 5 detik, bukan default TCP—baris 37.
- Menambahkan percobaan ulang ke
scp
pada baris 38—3 upaya yang masing-masing menunggu 1 detik.
Ada cara lain untuk mencoba lagi saat terjadi kesalahan.
Menunggu akhir dunia-bagaimana dan kapan harus mencoba lagi
Anda melihat ada pencobaan ulang yang ditambahkan ke scp
memerintah. Tapi itu mencoba ulang hanya untuk koneksi yang gagal, bagaimana jika perintah gagal di tengah penyalinan?
Terkadang Anda ingin gagal begitu saja karena sangat kecil peluang untuk pulih dari suatu masalah. Sebuah sistem yang memerlukan perbaikan perangkat keras, misalnya, atau Anda dapat gagal kembali ke mode terdegradasi—artinya Anda dapat melanjutkan pekerjaan sistem Anda tanpa data yang diperbarui. Dalam kasus tersebut, tidak masuk akal untuk menunggu selamanya tetapi hanya untuk jangka waktu tertentu.
Berikut adalah perubahan pada remote_copy
, agar singkat ini (versi empat):
#!/bin/bash# Kode dihilangkan untuk kejelasan...declare REMOTE_FILE="/var/log/lshw-dump.json"declare MAX_RETRIES=3# Blah blah blah...function remote_copy { lokal server=$1 percobaan lokal=$2 lokal sekarang=1 status=0 while [ $now -le $retry ]; do echo "INFO:Mencoba menyalin file dari:$server, effort=$now" /usr/bin/timeout --kill-after 25.0s 20.0s \ /usr/bin/scp \ -o \ logLevel=Error \ -o ConnectTimeout=5 \ -o ConnectionAttempts=3 \ ${server}:$REMOTE_FILE ${DATADIR}/lshw-json status ? jika [ $status -ne 0 ]; lalu sleep_time=$(((RANDOM % 60)+ 1)) echo "PERINGATAN:Penyalinan gagal untuk $server:$REMOTE_FILE. Menunggu '${sleep_time} detik' sebelum mencoba kembali..." /usr/bin/sleep ${sleep_time}s else istirahat # Baiklah, tidak ada gunanya menunggu... fi ((now=now+1)) selesai return $status}DATADIR="$HOME/Documents/lshw-dump"if [ ! -d "$DATADIR" ]; lalu /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL:Gagal membuat $DATADIR" &&keluar 100fideclare -Server_pidfor server di ${servers[*]}; lakukan remote_copy $server $MAX_RETRIES & server_pid[$server]=$! # Simpan PID dari scp dari server yang diberikan untuk nanti# Ulangi semua server dan:# Tunggu kode pengembalian masing-masing# Periksa kode keluar dari setiap server scpfor di ${!server_pid[*]}; apakah tunggu ${server_pid[$server]} menguji $? -ne 0 &&echo "ERROR:Salin dari $server bermasalah, tidak akan dilanjutkan" &&exit 100done# Blah bla bla, proses file yang baru saja Anda salin...
Bagaimana tampilannya sekarang? Dalam proses ini, saya memiliki satu sistem mati (mac-pro-1-1) dan satu sistem tanpa file (macmini2). Anda dapat melihat bahwa salinan dari server dmaf5 langsung berfungsi, tetapi untuk dua lainnya, ada percobaan ulang untuk waktu acak antara 1 dan 60 detik sebelum keluar:
INFO:Mencoba menyalin file dari:macmini2, effort=1INFO:Mencoba menyalin file dari:mac-pro-1-1, effort=1INFO:Mencoba menyalin file dari:dmaf5, effort=1scp:/var/log/lshw-dump.json:Tidak ada file atau direktori tersebutERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiPERINGATAN:Penyalinan gagal untuk macmini2:/var/log/lshw-dump.json. Menunggu '60 detik' sebelum mencoba kembali...ssh:sambungkan ke host mac-pro-1-1 port 22:Tidak ada rute ke hostERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiPERINGATAN:Penyalinan gagal untuk mac-pro -1-1:/var/log/lshw-dump.json. Menunggu '32 detik' sebelum mencoba kembali...INFO:Mencoba menyalin file dari:mac-pro-1-1, upaya=2ssh:sambungkan ke host mac-pro-1-1 port 22:Tidak ada rute ke hostERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiPERINGATAN:Penyalinan gagal untuk mac-pro-1-1:/var/log/lshw-dump.json. Menunggu '18 detik' sebelum mencoba kembali...INFO:Mencoba menyalin file dari:macmini2, effort=2scp:/var/log/lshw-dump.json:No such file or directoryERROR:Ada kesalahan dalam konteks utama , detail untuk diikutiPERINGATAN:Penyalinan gagal untuk macmini2:/var/log/lshw-dump.json. Menunggu '3 detik' sebelum mencoba kembali...INFO:Mencoba menyalin file dari:macmini2, effort=3scp:/var/log/lshw-dump.json:No such file or directoryERROR:Ada kesalahan dalam konteks utama , detail untuk diikutiPERINGATAN:Penyalinan gagal untuk macmini2:/var/log/lshw-dump.json. Menunggu '6 detik' sebelum mencoba kembali...INFO:Mencoba menyalin file dari:mac-pro-1-1, upaya=3ssh:sambungkan ke host mac-pro-1-1 port 22:Tidak ada rute ke hostERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiPERINGATAN:Penyalinan gagal untuk mac-pro-1-1:/var/log/lshw-dump.json. Menunggu '47 detik' sebelum mencoba kembali...ERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiERROR:Salin dari mac-pro-1-1 bermasalah, tidak akan dilanjutkan
Jika saya gagal, apakah saya harus mengulanginya lagi? Menggunakan pos pemeriksaan
Misalkan penyalinan jarak jauh adalah operasi paling mahal dari seluruh skrip ini dan Anda bersedia atau dapat menjalankan ulang skrip ini, mungkin menggunakan cron
atau melakukannya dengan tangan dua kali dalam sehari untuk memastikan Anda mengambil file jika satu atau beberapa sistem tidak berfungsi.
Anda dapat, untuk hari itu, membuat 'cache status' kecil, di mana Anda hanya merekam operasi pemrosesan yang berhasil per mesin. Jika ada sistem di sana, jangan repot-repot memeriksa lagi untuk hari itu.
Beberapa program, seperti Ansible, melakukan hal serupa dan memungkinkan Anda untuk mencoba kembali buku pedoman pada sejumlah mesin terbatas setelah kegagalan (--limit @/home/user/site.retry
).
Versi baru (versi lima) skrip memiliki kode untuk merekam status salinan (baris 15-33):
15 mendeklarasikan SCRIPT_NAME=$(/usr/bin/basename $BASH_SOURCE)|| exit 10016 nyatakan YYYYMMDD=$(/usr/bin/date +%Y%m%d)|| exit 10017 mendeklarasikan CACHE_DIR="/tmp/$SCRIPT_NAME/$YYYYMMDD"18 # Logika untuk membersihkan direktori cache setiap hari tidak ditampilkan di sini19 jika [ ! -d "$CACHE_DIR" ]; lalu20 /usr/bin/mkdir -p -v "$CACHE_DIR"|| exit 10021 fi22 trap "/bin/rm -rf $CACHE_DIR" fungsi INT KILL2324 check_previous_run {25 mesin lokal=$126 test -f $CACHE_DIR/$machine &&return 0|| return 127 }2829 function mark_previous_run {30 machine=$131 /usr/bin/touch $CACHE_DIR/$machine32 return $?33 }
Apakah Anda memperhatikan jebakan di jalur 22? Jika skrip terputus (mati), saya ingin memastikan seluruh cache tidak valid.
Dan kemudian, tambahkan logika pembantu baru ini ke dalam remote_copy
fungsi (baris 52-81):
52 function remote_copy {53 server lokal=$154 check_previous_run $server55 test $? -eq 0 &&echo "INFO:$1 berhasil dijalankan sebelumnya. Tidak melakukan lagi" &&return 056 local retries=$257 local now=158 status=059 while [ $now -le $retries ]; do60 echo "INFO:Mencoba menyalin file dari:$server, effort=$now"61 /usr/bin/timeout --kill-after 25.0s 20.0s \62 /usr/bin/scp \63 -o Batch \64 -o logLevel=Error \65 -o ConnectTimeout=5 \66 -o ConnectionAttempts=3 \67 $ ${FILE_} $status -ne 0 ]; then70 sleep_time=$(((RANDOM % 60)+ 1))71 echo "PERINGATAN:Penyalinan gagal untuk $server:$REMOTE_FILE. Menunggu '${sleep_time} detik' sebelum mencoba kembali..."72 /usr/bin /sleep ${sleep_time}s73 else74 istirahat # Baiklah, tidak ada gunanya menunggu...75 fi76 ((now=now+1))77 done78 test $status -eq 0 &&mark_previous_run $? -ne 0 &&status=180 kembalikan $status81 }
Saat pertama kali dijalankan, pesan baru untuk direktori cache akan dicetak:
./collect_data_from_servers.v5.sh/usr/bin/mkdir:direktori yang dibuat '/tmp/collect_data_from_servers.v5.sh'/usr/bin/mkdir:direktori yang dibuat '/tmp/collect_data_from_servers.v5.sh //20210612'ERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiINFO:Mencoba menyalin file dari:macmini2, upaya=1ERROR:Ada kesalahan dalam konteks utama, detail untuk diikuti
Jika Anda menjalankannya lagi, skrip mengetahui bahwa dma5f baik untuk pergi, tidak perlu mencoba lagi salinan:
./collect_data_from_servers.v5.shINFO:dmaf5 berhasil dijalankan sebelumnya. Tidak melakukan lagiERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiINFO:Mencoba menyalin file dari:macmini2, upaya=1ERROR:Ada kesalahan dalam konteks utama, detail untuk diikutiINFO:Mencoba menyalin file dari:mac-pro- 1-1, upaya=1
Bayangkan bagaimana kecepatannya saat Anda memiliki lebih banyak mesin yang tidak boleh dikunjungi kembali.
Meninggalkan remah-remah di belakang:Apa yang harus dicatat, cara mencatat, dan keluaran verbose
Jika Anda seperti saya, saya suka sedikit konteks untuk dikorelasikan ketika terjadi kesalahan. gema
pernyataan pada skrip bagus tetapi bagaimana jika Anda dapat menambahkan stempel waktu ke dalamnya.
Jika Anda menggunakan logger
, Anda dapat menyimpan hasilnya di journalctl
untuk ditinjau nanti (bahkan agregasi dengan alat lain di luar sana). Bagian terbaiknya adalah Anda menunjukkan kekuatan journalctl
segera.
Jadi, alih-alih hanya melakukan echo
, Anda juga dapat menambahkan panggilan ke logger
seperti ini menggunakan fungsi bash baru yang disebut ‘message
':
SCRIPT_NAME=$(/usr/bin/basename $BASH_SOURCE)|| keluar 100FULL_PATH=$(/usr/bin/realpath ${BASH_SOURCE[0]})|| exit 100set -o errtrace # Aktifkan jebakan err, kode akan dipanggil ketika kesalahan terdeteksitrap "echo ERROR:Ada kesalahan di ${FUNCNAME[0]-main context}, detail menyusul" ERRdeclare CACHE_DIR="/tmp /$SCRIPT_NAME/$YYYYMMDD"function message { message="$1" func_name="${2-unknown}" priority=6 if [ -z "$2" ]; lalu echo "INFO:" $message else echo "ERROR:" $message priority=0 fi /usr/bin/logger --journald<
Anda dapat melihat bahwa Anda dapat menyimpan kolom terpisah sebagai bagian dari pesan, seperti prioritas, skrip yang menghasilkan pesan, dll.
Jadi bagaimana ini berguna? Nah, Anda bisa mendapatkan
pesan antara 13:26 dan 13:27, hanya kesalahan (priority=0
) dan hanya untuk skrip kami (collect_data_from_servers.v6.sh
) seperti ini, output dalam format JSON:
journalctl --sejak 13:26 --sampai 13:27 --output json-pretty PRIORITY=0 MESSAGE_ID=collect_data_from_servers.v6.sh
{ "_BOOT_ID" :"dfcda9a1a1cd406ebd88a339bec96fb6", "_AUDIT_LOGINUID" :"1000", "SYSLOG_IDENTIFIER" :"TRANS", 0 SEL :"unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023", "__REALTIME_TIMESTAMP" :"1623518797641880", "_AUDIT_SESSION" :"3", ESS_ "_GID" sh", "MESSAGE" :"Pesan gagal untuk macmini2:/var/log/lshw-dump.json. Menunggu '45 detik' sebelum mencoba kembali...", "_CAP_EFFECTIVE" :"0", "CODE_FUNC" :"remote_copy", "_MACHINE_ID" :"60d7a3f69b674aaebb600c0e82e01d05", "_COMM" :"logger", "CODE_FILE" :"/home/josevnz/BashError_/collect_data"_P32sh . :"25928272252", "_HOSTNAME" :"dmaf5", "_SOURCE_REALTIME_TIMESTAMP" :"1623518797641843", "__CURSOR" :"s=97bb6295795a4560ad6f dedd8143df97;i=1f826;b=dfcda9a1a1cd406ebd88a339bec96fb6;m=60972097c;t=5c494ed383898;x=921c71966b8943e3", "_UID" :"1000"}
Karena ini adalah data terstruktur, pengumpul log lain dapat memeriksa semua mesin Anda, menggabungkan log skrip, dan kemudian Anda tidak hanya memiliki data tetapi juga informasi.
Anda dapat melihat seluruh versi enam skrip.
Jangan terlalu bersemangat untuk mengganti data Anda sampai Anda memeriksanya.
Jika Anda perhatikan sejak awal, saya telah menyalin file JSON yang rusak berulang kali:
Kesalahan penguraian:Pemisah yang diharapkan antara nilai pada baris 4, penguraian kolom 11ERROR '/home/josevnz/Documents/lshw-dump/lshw-dmaf5-dump.json'
Itu mudah untuk dicegah. Salin file ke lokasi sementara dan jika file rusak, jangan coba mengganti versi sebelumnya (dan biarkan yang buruk untuk diperiksa. baris 99-107 dari versi tujuh skrip):
function remote_copy { server lokal=$1 check_previous_run $server test $? -eq 0 &&pesan "$1 berhasil dijalankan sebelumnya. Tidak melakukan lagi" &&return 0 local retries=$2 local now=1 status=0 while [ $now -le $retries ]; do pesan "Mencoba menyalin file dari:$server, effort=$now" /usr/bin/timeout --kill-after 25.0s 20.0s \ /usr/bin/scp \ -o \ =yes Kesalahan \ -o ConnectTimeout=5 \ -o ConnectionAttempts=3 \ ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump status $. jika [ $status -ne 0 ]; lalu sleep_time=$(((RANDOM % 60)+ 1)) pesan "Penyalinan gagal untuk $server:$REMOTE_FILE. Menunggu '${sleep_time} detik' sebelum mencoba kembali..." ${FUNCNAME[0]} / usr/bin/sleep ${sleep_time}s else istirahat # Baiklah, tidak ada gunanya menunggu... fi ((now=now+1)) selesai if [ $status -eq 0 ]; lalu /usr/bin/jq '.' ${DATADIR}/lshw-$server-dump.json.$$> /dev/null 2>&1 status=$? jika [ $status -eq 0 ]; lalu /usr/bin/mv -v -f ${DATADIR}/lshw-$server-dump.json.$$ ${DATADIR}/lshw-$server-dump.json &&mark_previous_run $server test $? -ne 0 &&status=1 else pesan "${DATADIR}/lshw-$server-dump.json.$$ Rusak. Meninggalkan untuk diperiksa..." ${FUNCNAME[0]} fi fi return $status}
Pilih alat yang tepat untuk tugas tersebut dan siapkan kode Anda dari baris pertama
Salah satu aspek yang sangat penting dari penanganan kesalahan adalah pengkodean yang tepat. Jika Anda memiliki logika yang buruk dalam kode Anda, tidak ada penanganan kesalahan yang akan membuatnya lebih baik. Agar ini tetap singkat dan terkait dengan bash, saya akan memberi Anda beberapa petunjuk di bawah ini.
Anda harus SELALU memeriksa sintaks kesalahan sebelum menjalankan skrip Anda:
bash -n $my_bash_script.sh
Dengan serius. Itu harus otomatis seperti melakukan tes lainnya.
Baca halaman manual bash dan kenali opsi yang harus diketahui, seperti:
set -xvmy_complicated_instruction1my_complicated_instruction2my_complicated_instruction3set +xv
Gunakan ShellCheck untuk memeriksa skrip bash Anda
Sangat mudah untuk melewatkan masalah sederhana ketika skrip Anda mulai tumbuh besar. ShellCheck adalah salah satu alat yang menyelamatkan Anda dari membuat kesalahan.
shellcheck collect_data_from_servers.v7.shIn collect_data_from_servers.v7.sh baris 15:untuk ketergantungan pada ${dependencies[@]}; lakukan ^----------------^ SC2068:Ekspansi larik tanda kutip ganda untuk menghindari elemen pemisahan ulang.Dalam baris collect_data_from_servers.v7.sh 16: if [ ! -x $ketergantungan ]; lalu ^---------^ SC2086:Tanda kutip ganda untuk mencegah penggelembungan dan pemisahan kata. Apakah maksud Anda: if [ ! -x "$ketergantungan" ]; lalu...
Jika Anda bertanya-tanya, versi final skrip, setelah melewati ShellCheck ada di sini. Sangat bersih.
Anda melihat sesuatu dengan proses scp latar belakang
Anda mungkin memperhatikan bahwa jika Anda mematikan skrip, itu meninggalkan beberapa proses bercabang di belakang. Itu tidak baik dan inilah salah satu alasan saya lebih suka menggunakan alat seperti Ansible atau Parallel untuk menangani jenis tugas ini di beberapa host, membiarkan kerangka kerja melakukan pembersihan yang tepat untuk saya. Anda tentu saja dapat menambahkan lebih banyak kode untuk menangani situasi ini.
Skrip bash ini berpotensi membuat bom garpu. Itu tidak memiliki kendali atas berapa banyak proses yang muncul pada saat yang sama, yang merupakan masalah besar dalam lingkungan produksi nyata. Juga, ada batasan berapa banyak sesi ssh bersamaan yang dapat Anda miliki (apalagi mengkonsumsi bandwidth). Sekali lagi, saya menulis contoh fiktif ini di bash untuk menunjukkan kepada Anda bagaimana Anda selalu dapat meningkatkan program untuk menangani kesalahan dengan lebih baik.
Mari kita rekap
[ Unduh sekarang:Panduan sysadmin untuk skrip Bash. ]
1. Anda harus memeriksa kode pengembalian dari perintah Anda. Itu bisa berarti memutuskan untuk mencoba lagi sampai kondisi sementara membaik atau membuat hubungan pendek seluruh skrip.
2. Berbicara tentang kondisi sementara, Anda tidak perlu memulai dari awal. Anda dapat menyimpan status tugas yang berhasil, lalu mencoba lagi sejak saat itu dan seterusnya.
3. Bash 'perangkap' adalah teman Anda. Gunakan untuk pembersihan dan penanganan kesalahan.
4. Saat mengunduh data dari sumber apa pun, anggap itu rusak. Jangan pernah menimpa kumpulan data yang baik dengan data baru sampai Anda melakukan beberapa pemeriksaan integritas.
5. Manfaatkan journalctl dan bidang khusus. Anda dapat melakukan penelusuran canggih untuk mencari masalah, dan bahkan mengirimkan data tersebut ke agregator log.
6. Anda dapat memeriksa status tugas latar belakang (termasuk sub-kulit). Ingatlah untuk menyimpan PID dan menunggunya.
7. Dan terakhir:Gunakan pembantu lint Bash seperti ShellCheck. Anda dapat menginstalnya di editor favorit Anda (seperti VIM atau PyCharm). Anda akan terkejut betapa banyak kesalahan yang tidak terdeteksi pada skrip Bash...
Jika Anda menikmati konten ini atau ingin mengembangkannya, hubungi tim di [email protected].