Funksiyalar, argumentlar va xatolarni boshqarish — set -euo

~15 daq · Linux · Bash skriptlar

Muammodan boshlaylik

37-darsdagi deploy skripti:

cd /opt/dokon
git pull
npm ci              # ← internet uzildi, xato!
npm run build       # ← node_modules yo'q — xato, lekin davom etadi
sudo systemctl restart dokon    # ← buzilgan build bilan qayta ishga tushirildi!

Bash standart holatda xatolarga e'tibor bermaydi — keyingi qatorga o'tib ketadi. Natija: sayt ishlamay qoldi, skript esa "muvaffaqiyatli" tugadi.

Bu darsda — skriptni ishonchli qilish: xatoda to'xtash, tozalash, funksiyalar bilan tartib.


set -euo pipefail — xavfsiz rejim

Har bir jiddiy skriptning ikkinchi qatori:

#!/bin/bash
set -euo pipefail
Bayroq Nima qiladi
-e Har qanday buyruq xato bersa (kod ≠ 0) — darhol to'xtash
-u E'lon qilinmagan o'zgaruvchidan foydalanish — xato
-o pipefail Pipe dagi har qanday buyruq xatosi — butun pipe xatosi

-u nima uchun muhim

rm -rf "$PAPKA/"*        # PAPKA da xato bor (PAPAK deb yozilgan)

-u siz: $PAPKA bo'sh → rm -rf /*. -u bilan: PAPKA: unbound variable — skript to'xtaydi.

pipefail nima uchun muhim

curl -s https://api.example.com/data | jq '.items'

curl muvaffaqiyatsiz bo'lsa ham, pipe ning chiqish kodi — oxirgi buyruqniki (jq). pipefail bilan — har qanday qismning xatosi hisobga olinadi.

Kutilgan xatolar

Ba'zan xato normal (masalan, grep hech narsa topmasa — kod 1):

set -e
grep "ERROR" app.log | wc -l          # topmasa — skript to'xtaydi!

SONI=$(grep -c "ERROR" app.log || true)    # || true — xatoni "yutish"
if grep -q "ERROR" app.log; then ...       # if ichida -e ishlamaydi — xavfsiz

Xatoni ushlash: trap

#!/bin/bash
set -euo pipefail

trap 'echo "✗ Xato: $LINENO-qatorda, buyruq: $BASH_COMMAND" >&2' ERR

Xato bo'lganda qaysi qatorda va qaysi buyruqda — aniq ko'rsatadi.

Tozalash: EXIT

Vaqtinchalik fayllar, lock-fayllar — skript qanday tugashidan qat'i nazar o'chirilishi kerak:

VAQTINCHA=$(mktemp -d)
trap 'rm -rf "$VAQTINCHA"' EXIT

# ... VAQTINCHA da ishlash ...
# Skript muvaffaqiyatli tugasa ham, xato bilan to'xtasa ham, Ctrl+C bosilsa ham — papka o'chiriladi

mktemp -d — noyob nomli vaqtinchalik papka (/tmp/tmp.Xa8Kj3).

Bir vaqtda faqat bitta nusxa

Cron har 5 daqiqada skriptni ishga tushiradi, lekin u ba'zan 10 daqiqa ishlaydi — ikki nusxa bir-biriga xalaqit beradi:

exec 200>/var/lock/zaxira.lock
flock -n 200 || { echo "Boshqa nusxa ishlayapti"; exit 1; }

Funksiyalar

salomlash() {
  echo "Salom, $1!"
}

salomlash "Otabek"
salomlash "Malika"
  • Funksiya ichida $1, $2, $@ — funksiyaning argumentlari (skriptniki emas)
  • Avval e'lon qilinadi, keyin chaqiriladi

local — lokal o'zgaruvchilar

hisobla() {
  local A="$1"
  local B="$2"
  local NATIJA=$(( A + B ))
  echo "$NATIJA"
}

JAVOB=$(hisobla 5 7)
echo "Javob: $JAVOB"         # 12

local siz — o'zgaruvchilar global bo'ladi va skriptning boshqa qismlarini buzishi mumkin. Funksiya ichida doim local.

Qiymat qaytarish

Bash funksiyasi ikki xil "qaytaradi":

  • Natija — echo orqali; $(funksiya) bilan olinadi
  • Holat — return 0 / return 1 (chiqish kodi); if funksiya; then bilan tekshiriladi
ishlayaptimi() {
  curl -sf "localhost:$1/health" > /dev/null
}

if ishlayaptimi 3000; then
  echo "API ishlayapti"
fi

Oxirgi buyruqning chiqish kodi — funksiyaning chiqish kodi.


Yaxshi skript shabloni

#!/usr/bin/env bash
#
# zaxira.sh — MySQL bazasining zaxira nusxasini olish
# Foydalanish: ./zaxira.sh <baza_nomi> [saqlash_kunlari]
#
set -euo pipefail

# --- Sozlamalar ---
readonly ZAXIRA_PAPKA="/var/backups/mysql"
readonly LOG="/var/log/zaxira.log"

# --- Yordamchi funksiyalar ---
log() {
  echo "[$(date '+%F %T')] $*" | tee -a "$LOG"
}

xato() {
  log "XATO: $*" >&2
  exit 1
}

foydalanish() {
  echo "Foydalanish: $0 <baza_nomi> [saqlash_kunlari]"
  echo "Misol:       $0 dokon 14"
  exit 1
}

# --- Argumentlar ---
[[ $# -lt 1 ]] && foydalanish
readonly BAZA="$1"
readonly KUNLAR="${2:-7}"

[[ "$KUNLAR" =~ ^[0-9]+$ ]] || xato "Kunlar soni raqam bo'lishi kerak: $KUNLAR"

# --- Asosiy ish ---
main() {
  log "Zaxira boshlandi: $BAZA"
  mkdir -p "$ZAXIRA_PAPKA"

  local fayl="$ZAXIRA_PAPKA/${BAZA}_$(date +%F_%H%M).sql.gz"
  mysqldump --single-transaction "$BAZA" | gzip > "$fayl"

  [[ -s "$fayl" ]] || xato "Zaxira fayli bo'sh: $fayl"
  log "Saqlandi: $fayl ($(du -h "$fayl" | cut -f1))"

  local ochirildi
  ochirildi=$(find "$ZAXIRA_PAPKA" -name "${BAZA}_*.sql.gz" -mtime +"$KUNLAR" -print -delete | wc -l)
  log "Eski nusxalar o'chirildi: $ochirildi ta"
}

main "$@"

Tuzilma:

  1. Shebang va izoh — nima qiladi, qanday ishlatiladi
  2. set -euo pipefail
  3. Sozlamalar — readonly (o'zgarmas)
  4. Yordamchi funksiyalar — log, xato
  5. Argumentlarni tekshirish
  6. main funksiyasi va oxirida main "$@"

$* — barcha argumentlar bitta satr sifatida (log xabarlari uchun qulay).


Debug

bash -x skript.sh           # har bir buyruqni bajarishdan oldin ko'rsatish
+ BAZA=dokon
+ KUNLAR=7
+ mkdir -p /var/backups/mysql
...

Skript ichida faqat bir qismi uchun:

set -x
# ... muammoli qism ...
set +x

ShellCheck — avtomatik tekshiruv

sudo apt install shellcheck
shellcheck zaxira.sh
In zaxira.sh line 12:
rm -rf $PAPKA/*
       ^-----^ SC2086: Double quote to prevent globbing and word splitting.

Qo'shtirnoqsiz o'zgaruvchilar va boshqa ko'plab tipik xatolarni topadi. VS Code uchun ShellCheck kengaytmasi ham bor — yozish paytida ogohlantiradi. Har bir skriptni tekshiring.


Nima uchun shunday?

Nima uchun bash standart holatda xatolarda to'xtamaydi — bu xavfli emasmi?

Tarixiy sabab: bash avval interaktiv shell — terminalda bitta buyruq xato bersa, butun sessiya yopilib ketmasligi kerak. Skriptlar uchun ham shu xatti-harakat meros qoldi va orqaga moslik uchun o'zgartirilmadi. Shuning uchun set -euo pipefail — skriptlar uchun "zamonaviy standart": tajribali muhandislar uni deyarli har bir skriptga avtomatik yozadi. Bu — "tez va baland ovozda muvaffaqiyatsizlikka uchra" (fail fast) tamoyili: xatoni darhol ko'rish, uni yashirib, keyinroq sirli oqibatlarini tekshirishdan ko'ra ancha yaxshi.


Xulosa

Eslab qoling:

  • set -euo pipefail — har bir skriptda; kutilgan xatolar — || true yoki if
  • trap '...' ERR — xato qatorini ko'rsatish; trap '...' EXIT — tozalash
  • mktemp -d — vaqtinchalik papka; flock — bitta nusxa
  • funksiya() { ... }; argumentlar — $1; ichida doim local
  • Natija — echo + $(...); holat — return 0/1 + if
  • Shablon: izoh → set → readonly sozlamalar → log/xato → argumentlar → main "$@"
  • bash -x — debug; shellcheck — har bir skriptni tekshiring

Keyingi dars

40-dars: Rejalashtirish — cron va systemd timer