Βέλτιστες πρακτικές σεναρίου Bash: Ένας γρήγορος οδηγός για αξιόπιστα σενάρια Bash και απόδοση

Βέλτιστες πρακτικές σεναρίου Bash: Ένας γρήγορος οδηγός για αξιόπιστα σενάρια Bash και απόδοση
Ταπετσαρία κέλυφος από manapi

Η αποσφαλμάτωση των σεναρίων bash είναι σαν να ψάχνετε για μια βελόνα σε μια θημωνιά, ειδικά όταν εμφανίζονται νέες προσθήκες στην υπάρχουσα βάση κώδικα χωρίς έγκαιρη εξέταση θεμάτων δομής, καταγραφής και αξιοπιστίας. Μπορείτε να βρεθείτε σε τέτοιες καταστάσεις είτε λόγω δικών σας λαθών είτε όταν διαχειρίζεστε πολύπλοκους σωρούς σεναρίων.

Ομάδα Mail.ru Cloud Solutions μετέφρασε ένα άρθρο με συστάσεις που θα σας βοηθήσουν να γράψετε, να διορθώσετε και να διατηρήσετε καλύτερα τα σενάρια σας. Είτε το πιστεύετε είτε όχι, τίποτα δεν ξεπερνά την ικανοποίηση της σύνταξης καθαρού, έτοιμου προς χρήση κώδικα bash που λειτουργεί κάθε φορά.

Στο άρθρο, ο συγγραφέας μοιράζεται ό,τι έμαθε τα τελευταία χρόνια, καθώς και μερικά κοινά λάθη που τον έχουν ακινητοποιήσει. Αυτό είναι σημαντικό γιατί κάθε προγραμματιστής λογισμικού, σε κάποιο σημείο της καριέρας του, εργάζεται με σενάρια για να αυτοματοποιήσει εργασίες ρουτίνας.

Χειριστές παγίδων

Τα περισσότερα σενάρια bash που έχω συναντήσει δεν χρησιμοποιούν ποτέ αποτελεσματικό μηχανισμό εκκαθάρισης όταν συμβαίνει κάτι απροσδόκητο κατά την εκτέλεση του σεναρίου.

Εκπλήξεις μπορεί να προκύψουν από το εξωτερικό, όπως η λήψη ενός σήματος από τον πυρήνα. Ο χειρισμός τέτοιων περιπτώσεων είναι εξαιρετικά σημαντικός για να διασφαλιστεί ότι τα σενάρια είναι αρκετά αξιόπιστα ώστε να εκτελούνται σε συστήματα παραγωγής. Συχνά χρησιμοποιώ χειριστές εξόδου για να απαντήσω σε σενάρια όπως αυτό:

function handle_exit() {
  // Add cleanup code here
  // for eg. rm -f "/tmp/${lock_file}.lock"
  // exit with an appropriate status code
}
  
// trap <HANDLER_FXN> <LIST OF SIGNALS TO TRAP>
trap handle_exit 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

trap είναι μια ενσωματωμένη εντολή κελύφους που σας βοηθά να καταχωρίσετε μια συνάρτηση καθαρισμού που καλείται σε περίπτωση οποιουδήποτε σήματος. Ωστόσο, θα πρέπει να δίνεται ιδιαίτερη προσοχή σε χειριστές όπως π.χ SIGINT, που προκαλεί την ακύρωση του σεναρίου.

Επιπλέον, στις περισσότερες περιπτώσεις θα πρέπει να πιάνεις μόνο EXIT, αλλά η ιδέα είναι ότι μπορείτε πραγματικά να προσαρμόσετε τη συμπεριφορά του σεναρίου για κάθε μεμονωμένο σήμα.

Ενσωματωμένες λειτουργίες συνόλου - γρήγορος τερματισμός σε περίπτωση σφάλματος

Είναι πολύ σημαντικό να ανταποκρίνεστε σε σφάλματα μόλις συμβούν και να σταματήσετε γρήγορα την εκτέλεση. Τίποτα δεν θα μπορούσε να είναι χειρότερο από το να συνεχίσετε να εκτελείτε μια εντολή όπως αυτή:

rm -rf ${directory_name}/*

Σημειώστε ότι η μεταβλητή directory_name δεν καθορίζεται.

Είναι σημαντικό να χρησιμοποιείτε ενσωματωμένες λειτουργίες για να χειρίζεστε τέτοια σενάρια set, Όπως set -o errexit, set -o pipefail ή set -o nounset στην αρχή του σεναρίου. Αυτές οι λειτουργίες διασφαλίζουν ότι το σενάριό σας θα βγει αμέσως μόλις συναντήσει οποιονδήποτε μη μηδενικό κωδικό εξόδου, χρήση ακαθόριστων μεταβλητών, μη έγκυρες εντολές που περνούν πάνω από έναν σωλήνα και ούτω καθεξής:

#!/usr/bin/env bash

set -o errexit
set -o nounset
set -o pipefail

function print_var() {
  echo "${var_value}"
}

print_var

$ ./sample.sh
./sample.sh: line 8: var_value: unbound variable

Σημείωση: ενσωματωμένες λειτουργίες όπως set -o errexit, θα βγει από το σενάριο μόλις υπάρξει ένας "ακατέργαστος" κωδικός επιστροφής (εκτός από μηδέν). Επομένως, είναι καλύτερο να εισαγάγετε προσαρμοσμένο χειρισμό σφαλμάτων, για παράδειγμα:

#!/bin/bash
error_exit() {
  line=$1
  shift 1
  echo "ERROR: non zero return code from line: $line -- $@"
  exit 1
}
a=0
let a++ || error_exit "$LINENO" "let operation returned non 0 code"
echo "you will never see me"
# run it, now we have useful debugging output
$ bash foo.sh
ERROR: non zero return code from line: 9 -- let operation returned non 0 code

Η σύνταξη σεναρίων με αυτόν τον τρόπο σάς αναγκάζει να είστε πιο προσεκτικοί σχετικά με τη συμπεριφορά όλων των εντολών στο σενάριο και να προβλέψετε την πιθανότητα λάθους πριν σας εκπλήξει.

ShellCheck για τον εντοπισμό σφαλμάτων κατά την ανάπτυξη

Αξίζει να ενσωματώσετε κάτι σαν Έλεγχος κελύφους στους αγωγούς ανάπτυξης και δοκιμής σας για να ελέγξετε τον κώδικα bash σας σε σχέση με τις βέλτιστες πρακτικές.

Το χρησιμοποιώ στα τοπικά περιβάλλοντα ανάπτυξης μου για να λαμβάνω αναφορές σχετικά με τη σύνταξη, τη σημασιολογία και ορισμένα σφάλματα στον κώδικα που μπορεί να είχα χάσει κατά την ανάπτυξη. Αυτό είναι ένα εργαλείο στατικής ανάλυσης για τα σενάρια bash σας και συνιστώ ανεπιφύλακτα να το χρησιμοποιήσετε.

Χρησιμοποιώντας τους δικούς σας κωδικούς εξόδου

Οι κωδικοί επιστροφής στο POSIX δεν είναι απλώς μηδέν ή ένα, αλλά μηδενικές ή μη μηδενικές τιμές. Χρησιμοποιήστε αυτές τις δυνατότητες για να επιστρέψετε προσαρμοσμένους κωδικούς σφάλματος (μεταξύ 201-254) για διάφορες περιπτώσεις σφαλμάτων.

Αυτές οι πληροφορίες μπορούν στη συνέχεια να χρησιμοποιηθούν από άλλα σενάρια που αναδιπλώνουν τα δικά σας για να κατανοήσετε ακριβώς τι είδους σφάλμα συνέβη και να αντιδράσετε ανάλογα:

#!/usr/bin/env bash

SUCCESS=0
FILE_NOT_FOUND=240
DOWNLOAD_FAILED=241

function read_file() {
  if ${file_not_found}; then
    return ${FILE_NOT_FOUND}
  fi
}

Σημείωση: παρακαλούμε να είστε ιδιαίτερα προσεκτικοί με τα ονόματα των μεταβλητών που ορίζετε για να αποφύγετε την κατά λάθος παράκαμψη μεταβλητών περιβάλλοντος.

Λειτουργίες καταγραφής

Η όμορφη και δομημένη καταγραφή είναι σημαντική για να κατανοήσετε εύκολα τα αποτελέσματα του σεναρίου σας. Όπως και με άλλες γλώσσες προγραμματισμού υψηλού επιπέδου, χρησιμοποιώ πάντα εγγενείς λειτουργίες καταγραφής στα σενάρια bash μου, όπως π.χ. __msg_info, __msg_error και ούτω καθεξής.

Αυτό βοηθά στην παροχή μιας τυποποιημένης δομής καταγραφής κάνοντας αλλαγές μόνο σε ένα μέρος:

#!/usr/bin/env bash

function __msg_error() {
    [[ "${ERROR}" == "1" ]] && echo -e "[ERROR]: $*"
}

function __msg_debug() {
    [[ "${DEBUG}" == "1" ]] && echo -e "[DEBUG]: $*"
}

function __msg_info() {
    [[ "${INFO}" == "1" ]] && echo -e "[INFO]: $*"
}

__msg_error "File could not be found. Cannot proceed"

__msg_debug "Starting script execution with 276MB of available RAM"

Συνήθως προσπαθώ να έχω κάποιο μηχανισμό στα σενάρια μου __init, όπου τέτοιες μεταβλητές καταγραφικού και άλλες μεταβλητές συστήματος αρχικοποιούνται ή ορίζονται σε προεπιλεγμένες τιμές. Αυτές οι μεταβλητές μπορούν επίσης να οριστούν από τις επιλογές της γραμμής εντολών κατά την επίκληση σεναρίου.

Για παράδειγμα, κάτι σαν:

$ ./run-script.sh --debug

Όταν εκτελείται ένα τέτοιο σενάριο, διασφαλίζει ότι οι ρυθμίσεις σε όλο το σύστημα έχουν οριστεί σε προεπιλεγμένες τιμές εάν απαιτούνται ή τουλάχιστον αρχικοποιούνται σε κάτι κατάλληλο εάν είναι απαραίτητο.

Συνήθως βασίζω την επιλογή του τι να αρχικοποιηθεί και τι όχι σε μια αντιστάθμιση μεταξύ της διεπαφής χρήστη και των λεπτομερειών των διαμορφώσεων στις οποίες μπορεί/πρέπει να εμβαθύνει ο χρήστης.

Αρχιτεκτονική για επαναχρησιμοποίηση και καθαρή κατάσταση συστήματος

Αρθρωτός/επαναχρησιμοποιήσιμος κωδικός

├── framework
│   ├── common
│   │   ├── loggers.sh
│   │   ├── mail_reports.sh
│   │   └── slack_reports.sh
│   └── daily_database_operation.sh

Διατηρώ ένα ξεχωριστό αποθετήριο που μπορώ να χρησιμοποιήσω για την προετοιμασία ενός νέου έργου/σεναρίου bash που θέλω να αναπτύξω. Οτιδήποτε μπορεί να επαναχρησιμοποιηθεί μπορεί να αποθηκευτεί σε ένα αποθετήριο και να ανακτηθεί από άλλα έργα που θέλουν να χρησιμοποιήσουν αυτήν τη λειτουργία. Η οργάνωση έργων με αυτόν τον τρόπο μειώνει σημαντικά το μέγεθος άλλων σεναρίων και επίσης διασφαλίζει ότι η βάση κώδικα είναι μικρή και εύκολη στη δοκιμή.

Όπως στο παραπάνω παράδειγμα, όλες οι λειτουργίες καταγραφής όπως π.χ __msg_info, __msg_error και άλλες, όπως οι αναφορές Slack, περιέχονται χωριστά στο common/* και συνδεθείτε δυναμικά σε άλλα σενάρια όπως daily_database_operation.sh.

Αφήστε πίσω ένα καθαρό σύστημα

Εάν φορτώνετε πόρους ενώ εκτελείται το σενάριο, συνιστάται η αποθήκευση όλων αυτών των δεδομένων σε έναν κοινόχρηστο κατάλογο με ένα τυχαίο όνομα, π.χ. /tmp/AlRhYbD97/*. Μπορείτε να χρησιμοποιήσετε τυχαίες γεννήτριες κειμένου για να επιλέξετε το όνομα του καταλόγου:

rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"

Μετά την ολοκλήρωση της εργασίας, η εκκαθάριση τέτοιων καταλόγων μπορεί να παρέχεται στους χειριστές γάντζων που συζητήθηκαν παραπάνω. Εάν δεν ληφθεί μέριμνα για τους προσωρινούς καταλόγους, συσσωρεύονται και σε κάποιο στάδιο προκαλούν απροσδόκητα προβλήματα στον κεντρικό υπολογιστή, όπως πλήρης δίσκος.

Χρήση αρχείων κλειδώματος

Συχνά πρέπει να βεβαιωθείτε ότι μόνο μία παρουσία ενός σεναρίου εκτελείται σε έναν κεντρικό υπολογιστή ανά πάσα στιγμή. Αυτό μπορεί να γίνει χρησιμοποιώντας αρχεία κλειδώματος.

Συνήθως δημιουργώ αρχεία κλειδώματος /tmp/project_name/*.lock και ελέγξτε για την παρουσία τους στην αρχή του σεναρίου. Αυτό βοηθά το σενάριο να τερματιστεί με χάρη και να αποφευχθούν απροσδόκητες αλλαγές στην κατάσταση του συστήματος από ένα άλλο σενάριο που εκτελείται παράλληλα. Τα αρχεία κλειδώματος δεν χρειάζονται εάν χρειάζεστε το ίδιο σενάριο να εκτελεστεί παράλληλα σε έναν δεδομένο κεντρικό υπολογιστή.

Μετρήστε και βελτιώστε

Συχνά χρειάζεται να δουλέψουμε με σενάρια που εκτελούνται σε μεγάλες χρονικές περιόδους, όπως καθημερινές λειτουργίες βάσης δεδομένων. Τέτοιες λειτουργίες συνήθως περιλαμβάνουν μια ακολουθία βημάτων: φόρτωση δεδομένων, έλεγχος για ανωμαλίες, εισαγωγή δεδομένων, αποστολή αναφορών κατάστασης και ούτω καθεξής.

Σε τέτοιες περιπτώσεις, προσπαθώ πάντα να σπάσω το σενάριο σε ξεχωριστά μικρά σενάρια και να αναφέρω την κατάστασή τους και τον χρόνο εκτέλεσής τους χρησιμοποιώντας:

time source "${filepath}" "${args}">> "${LOG_DIR}/RUN_LOG" 2>&1

Αργότερα μπορώ να δω τον χρόνο εκτέλεσης με:

tac "${LOG_DIR}/RUN_LOG.txt" | grep -m1 "real"

Αυτό με βοηθά να εντοπίσω προβληματικές/αργές περιοχές σε σενάρια που χρειάζονται βελτιστοποίηση.

Καλή τύχη!

Τι άλλο να διαβάσετε:

  1. Μετάβαση και κρυφές μνήμες GPU.
  2. Ένα παράδειγμα μιας εφαρμογής που βασίζεται σε συμβάντα που βασίζεται σε webhook στην αποθήκευση αντικειμένων S3 του Mail.ru Cloud Solutions.
  3. Το τηλεγραφικό μας κανάλι για τον ψηφιακό μετασχηματισμό.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο