Bash Komut Dosyası Oluşturma En İyi Uygulamaları: Güvenilir ve Performanslı Bash Komut Dosyaları İçin Hızlı Kılavuz

Bash Komut Dosyası Oluşturma En İyi Uygulamaları: Güvenilir ve Performanslı Bash Komut Dosyaları İçin Hızlı Kılavuz
Manapi'den kabuk duvar kağıdı

Bash komut dosyalarında hata ayıklamak samanlıkta iğne aramaya benzer; özellikle de yapı, kayıt tutma ve güvenilirlik sorunları zamanında dikkate alınmadan mevcut kod tabanında yeni eklemeler göründüğünde. Gerek kendi hatalarınız nedeniyle gerekse karmaşık senaryo yığınlarını yönetirken kendinizi bu tür durumların içinde bulabilirsiniz.

Ekip Mail.ru Bulut Çözümleri Komut dosyalarınızı daha iyi yazmanıza, hata ayıklamanıza ve korumanıza yardımcı olacak öneriler içeren bir makaleyi çevirdim. İster inanın ister inanmayın, her zaman işe yarayan, temiz, kullanıma hazır bash kodu yazmanın verdiği tatmini hiçbir şey yenemez.

Makalede yazar, son birkaç yılda öğrendiklerini ve kendisini hazırlıksız yakalayan bazı yaygın hataları paylaşıyor. Bu önemlidir çünkü her yazılım geliştiricisi kariyerinin bir noktasında rutin iş görevlerini otomatikleştirmek için komut dosyalarıyla çalışır.

Tuzak işleyicileri

Karşılaştığım çoğu bash betiği, betiğin yürütülmesi sırasında beklenmeyen bir şey olduğunda hiçbir zaman etkili bir temizleme mekanizması kullanmaz.

Çekirdekten sinyal alınması gibi dışarıdan sürprizler ortaya çıkabilir. Bu tür durumların ele alınması, komut dosyalarının üretim sistemlerinde çalışacak kadar güvenilir olmasını sağlamak açısından son derece önemlidir. Bunun gibi senaryolara yanıt vermek için sıklıkla çıkış işleyicilerini kullanırım:

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 herhangi bir sinyal durumunda çağrılan bir temizleme işlevini kaydetmenize yardımcı olan yerleşik bir kabuk komutudur. Ancak, aşağıdaki gibi işleyicilere özel dikkat gösterilmelidir: SIGINT, bu da betiğin iptal edilmesine neden olur.

Ayrıca çoğu durumda yalnızca yakalamanız gerekir EXIT, ancak buradaki fikir, betiğin davranışını her bir sinyal için gerçekten özelleştirebilmenizdir.

Yerleşik set fonksiyonları - hata durumunda hızlı sonlandırma

Hatalara meydana gelir gelmez müdahale etmek ve yürütmeyi hızla durdurmak çok önemlidir. Hiçbir şey bunun gibi bir komutu çalıştırmaya devam etmekten daha kötü olamaz:

rm -rf ${directory_name}/*

Lütfen değişkenin directory_name belirlenmedi.

Bu tür senaryoları yönetmek için yerleşik işlevleri kullanmak önemlidir set, Gibi set -o errexit, set -o pipefail veya set -o nounset senaryonun başında. Bu işlevler, betiğinizin sıfırdan farklı bir çıkış koduyla, tanımsız değişkenlerin kullanımıyla, bir kanal üzerinden geçirilen geçersiz komutlarla vb. karşılaştığında hemen çıkış yapmasını sağlar:

#!/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

Not: gibi yerleşik işlevler set -o errexit, "ham" bir dönüş kodu (sıfırdan farklı) olur olmaz komut dosyasından çıkar. Bu nedenle özel hata işlemeyi tanıtmak daha iyidir, örneğin:

#!/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

Komut dosyalarını bu şekilde yazmak, sizi komut dosyasındaki tüm komutların davranışları konusunda daha dikkatli olmaya ve bir hata sizi şaşırtmadan önce olasılığını tahmin etmeye zorlar.

Geliştirme sırasında hataları tespit etmek için ShellCheck

Gibi bir şeyi entegre etmeye değer KabukKontrol Bash kodunuzu en iyi uygulamalara göre kontrol etmek için geliştirme ve test işlem hatlarınıza.

Bunu yerel geliştirme ortamlarımda sözdizimi, anlambilim ve kodda geliştirme sırasında gözden kaçırmış olabileceğim bazı hatalar hakkında raporlar almak için kullanıyorum. Bu, bash betikleriniz için statik bir analiz aracıdır ve kullanmanızı şiddetle tavsiye ederim.

Kendi çıkış kodlarınızı kullanma

POSIX'teki dönüş kodları yalnızca sıfır veya bir değil, sıfır veya sıfır olmayan bir değerdir. Çeşitli hata durumları için özel hata kodları (201-254 arasında) döndürmek için bu özellikleri kullanın.

Bu bilgiler daha sonra tam olarak ne tür bir hatanın oluştuğunu anlamak ve buna göre tepki vermek için sizinkini saran diğer komut dosyaları tarafından kullanılabilir:

#!/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
}

Not: Ortam değişkenlerinin yanlışlıkla geçersiz kılınmasını önlemek için lütfen tanımladığınız değişken adlarına özellikle dikkat edin.

Günlüğe kaydetme işlevleri

Güzel ve yapılandırılmış günlük kaydı, komut dosyanızın sonuçlarını kolayca anlamak için önemlidir. Diğer üst düzey programlama dillerinde olduğu gibi, bash betiklerimde her zaman yerel günlük tutma işlevlerini kullanırım; __msg_info, __msg_error ve benzerleri.

Bu, yalnızca tek bir yerde değişiklik yaparak standartlaştırılmış bir günlük kaydı yapısının sağlanmasına yardımcı olur:

#!/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"

Senaryolarımda genellikle bir tür mekanizma bulundurmaya çalışırım __init, bu tür günlükçü değişkenleri ve diğer sistem değişkenleri başlatılır veya varsayılan değerlere ayarlanır. Bu değişkenler ayrıca komut dosyası çağrılması sırasında komut satırı seçeneklerinden de ayarlanabilir.

Örneğin şöyle bir şey:

$ ./run-script.sh --debug

Böyle bir komut dosyası çalıştırıldığında, sistem genelindeki ayarların, gerekiyorsa varsayılan değerlere ayarlanmasını veya en azından gerekiyorsa uygun bir şeye başlatılmasını sağlar.

Genellikle neyin başlatılacağı ve ne yapılmayacağı seçimini, kullanıcı arayüzü ile kullanıcının yapabileceği/araştırması gereken konfigürasyonların ayrıntıları arasındaki bir değiş tokuşa dayandırırım.

Yeniden kullanıma ve temiz sistem durumuna yönelik mimari

Modüler/yeniden kullanılabilir kod

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

Geliştirmek istediğim yeni bir proje/bash betiğini başlatmak için kullanabileceğim ayrı bir depo tutuyorum. Yeniden kullanılabilen her şey bir depoda saklanabilir ve bu işlevi kullanmak isteyen diğer projeler tarafından alınabilir. Projeleri bu şekilde düzenlemek, diğer komut dosyalarının boyutunu önemli ölçüde azaltır ve aynı zamanda kod tabanının küçük ve test edilmesinin kolay olmasını sağlar.

Yukarıdaki örnekte olduğu gibi tüm kayıt fonksiyonları __msg_info, __msg_error ve Slack raporları gibi diğerleri ayrı olarak yer almaktadır. common/* ve aşağıdaki gibi diğer senaryolara dinamik olarak bağlanın daily_database_operation.sh.

Arkanızda temiz bir sistem bırakın

Komut dosyası çalışırken herhangi bir kaynak yüklüyorsanız, bu tür tüm verileri rastgele bir adla paylaşılan bir dizinde saklamanız önerilir; /tmp/AlRhYbD97/*. Dizin adını seçmek için rastgele metin oluşturucuları kullanabilirsiniz:

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

İşin tamamlanmasının ardından yukarıda tartışılan kanca işleyicilerde bu tür dizinlerin temizlenmesi sağlanabilir. Geçici dizinlere dikkat edilmezse, bunlar birikir ve bir aşamada ana bilgisayarda tam disk gibi beklenmeyen sorunlara neden olur.

Kilit dosyalarını kullanma

Genellikle herhangi bir zamanda bir ana bilgisayarda bir komut dosyasının yalnızca bir örneğinin çalıştığından emin olmanız gerekir. Bu, kilit dosyaları kullanılarak yapılabilir.

Genellikle kilit dosyalarını oluştururum /tmp/project_name/*.lock ve betiğin başında bunların varlığını kontrol edin. Bu, betiğin düzgün bir şekilde sonlandırılmasına ve paralel çalışan başka bir betiğin sistem durumunda beklenmedik değişiklikler yapmasını engellemesine yardımcı olur. Belirli bir ana bilgisayarda aynı komut dosyasının paralel olarak yürütülmesine ihtiyacınız varsa kilit dosyalarına gerek yoktur.

Ölçün ve iyileştirin

Genellikle günlük veritabanı işlemleri gibi uzun süreler boyunca çalışan komut dosyalarıyla çalışmamız gerekir. Bu tür işlemler genellikle bir dizi adımı içerir: verilerin yüklenmesi, anormalliklerin kontrol edilmesi, verilerin içe aktarılması, durum raporlarının gönderilmesi vb.

Bu gibi durumlarda, her zaman betiği ayrı küçük betiklere ayırmaya çalışıyorum ve aşağıdakileri kullanarak durumlarını ve yürütme sürelerini rapor ediyorum:

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

Daha sonra yürütme süresini şu şekilde görebilirim:

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

Bu, komut dosyalarındaki optimizasyon gerektiren sorunlu/yavaş alanları belirlememe yardımcı oluyor.

İyi şanslar!

Okumak için başka ne var:

  1. Go ve GPU önbellekleri.
  2. Mail.ru Cloud Solutions'ın S3 nesne deposundaki web kancalarını temel alan olay odaklı bir uygulama örneği.
  3. Dijital dönüşümle ilgili telegram kanalımız.

Kaynak: habr.com

Yorum ekle