Funksiyalar
Shartlar va sikllar maqolasidagi check_service.sh misolida servis holatini tekshirish mantiqi faqat bitta servis uchun yozilgan edi. Agar bir nechta servisni xuddi shunday tekshirish kerak bo'lsa, o'sha bir necha qatorni har safar qayta yozish o'rniga ularni bir joyga yig'ib, nomi bilan chaqirish mumkin. Funksiya aynan shuni qiladi — u skript ichidagi qayta ishlatiladigan, alohida nomga ega kod bloki.
Nimalarni o'rganasiz
Bu maqola oxirida siz quyidagilarni qila olasiz:
- funksiyani e’lon qilish va chaqirishning ikkala sintaksisidan foydalanish;
- funksiyaga argument uzatish va ularni
$1,$@orqali o'qish; localkalit so'zi bilan o'zgaruvchining amal qilish doirasini (scope) cheklash;- funksiya ichida natijani
returnbilan qaytarish vaechobilan qaytarish o'rtasidagi farqni tushuntirish; - funksiyalarni turli skriptlarda qayta ishlatish uchun alohida faylga chiqarish.
Funksiyani e’lon qilish
Bash’da funksiyani e’lon qilishning ikkita teng huquqli sintaksisi bor:
Ikkalasi bir xil ishlaydi. Birinchi shakl POSIX bilan mos va boshqa shell’larda ham tanish, shuning uchun u ko'proq uchraydi. Funksiyani chaqirish oddiy buyruq nomini yozish bilan bir xil, qavslarsiz:
Warning
Funksiyani faqat u e’lon qilingan joydan keyingi qatorlarda chaqirish mumkin. Bash skriptni yuqoridan pastga o'qiydi va funksiya ta’rifiga yetib kelmaguncha uni tanimaydi. Shuning uchun funksiyalar odatda skript boshida, asosiy mantiqdan oldin joylashtiriladi.
Funksiyaga argument uzatish
Funksiya ham skriptning o'zi kabi positional parametrlarni qabul qiladi. Lekin bu parametrlar skriptning o'z $1, $2 lariga emas, funksiya chaqirilganda berilgan qiymatlarga tegishli:
$@ va $# ham funksiya ichida xuddi shunday ishlaydi, faqat funksiyaga uzatilgan argumentlarga nisbatan:
sum_all() {
local total=0
for number in "$@"; do
total=$((total + number))
done
echo "$total"
}
sum_all 3 5 7
local: o'zgaruvchining amal qilish doirasini cheklash
Bash’da standart holatda funksiya ichida e’lon qilingan o'zgaruvchi ham global hisoblanadi. U butun skript bo'ylab ko'rinadi va boshqa funksiyadagi yoki asosiy koddagi xuddi shu nomli o'zgaruvchini tasodifan qayta yozib yuborishi mumkin:
#!/bin/bash
count=10
increment() {
count=$((count + 1))
echo "Inside the function: $count"
}
increment
echo "After the function: $count"
Bu misolda count ataylab global sifatida ishlatilgan (funksiya tashqi holatga ta’sir qilishi kerak bo'lgan holatlar uchun bu to'g'ri). Lekin ko'pincha funksiya ichidagi vaqtinchalik o'zgaruvchi tashqariga "oqib chiqmasligi" kerak. Buning uchun local kalit so'zi ishlatiladi:
#!/bin/bash
count=10
increment() {
local count=0
count=$((count + 1))
echo "Inside the function: $count"
}
increment
echo "After the function: $count"
local bilan e’lon qilingan count faqat funksiya ichida yashaydi va tashqaridagi xuddi shu nomli o'zgaruvchiga umuman ta’sir qilmaydi. Yuqoridagi sum_all misolidagi total ham aynan shu sababli local qilib e’lon qilingan. Aks holda u boshqa funksiyadagi yoki asosiy skriptdagi total o'zgaruvchisi bilan to'qnashishi mumkin edi.
Tip
Amaliy qoida: funksiya ichida yaratilgan va faqat shu funksiyaga tegishli har bir o'zgaruvchini local qilib e’lon qiling. Bu funksiyani yon ta’sirsiz (side-effect-free) va oldindan aytib bo'ladigan qiladi: uni o'qigan har bir kishi u tashqi o'zgaruvchilarga ta’sir qilmasligiga ishonch hosil qila oladi.
local x=$(cmd) tuzog'i
local command substitution bilan bitta qatorda birlashtirilganda nozik bir muammo paydo bo'ladi. Bu modulning keyingi loyiha skriptlarida ikkalasi aynan shu sababli alohida yoziladi. Quyidagini ko'rib chiqaylik:
Bu yerda local ning o'zi ham buyruq va u date dan keyin ishlaydi. Shuning uchun u date ning exit statusini o'zinikiga almashtirib yuboradi (local esa deyarli har doim muvaffaqiyatli tugaydi). Natijada $( ) ichidagi buyruqning muvaffaqiyatsizligi jimgina yo'qoladi. Bu esa set -e ni ham, shu qiymat berish (assignment) ustidagi har qanday if/exit kod tekshiruvini ham befoyda qiladi. Yechim — avval e’lon qilish, keyin qiymat berish:
Shuning uchun Backup skripti ularni birlashtirmasdan, bir qatorda local timestamp, keyingi qatorda esa timestamp=$(date ...) deb yozadi. shellcheck bir qatorli shaklni aynan shu sababli SC2155 sifatida belgilaydi.
Tip
Ikki qatorli shakl faqat almashtirilgan (substitution) buyruq muvaffaqiyatli tugaganmi yoki yo'qmi, sizga muhim bo'lgandagina ahamiyatli. To'liq ishonadigan qiymat uchun (literal, $1) bir qatorli local x="$1" bemalol ishlatiladi. Shubha bo'lsa yoki set -euo pipefail yoqilgan bo'lsa, e’lon qilishni qiymat berishdan ajrating.
Natijani qaytarish: return va echo
Bash funksiyasi ikki xil "natija" qaytara oladi va ular ko'pincha chalkashtiriladi:
return — faqat 0 dan 255 gacha bo'lgan butun sonni, ya’ni funksiyaning exit kodini qaytaradi. U qiymatni emas, holatni (muvaffaqiyat/muvaffaqiyatsizlik) bildiradi:
is_running() {
systemctl is-active --quiet "$1"
return $?
}
if is_running "nginx"; then
echo "nginx is running"
fi
Aslida bu yerda return $? ortiqcha: systemctl is-active funksiyaning oxirgi buyrug'i bo'lgani uchun uning exit kodi avtomatik ravishda funksiya natijasiga aylanadi. Shuning uchun return $? ni butunlay olib tashlash mumkin.
Matn yoki son qiymatini qaytarish uchun return ishlatilmaydi (u 0–255 bilan cheklangan). Buning o'rniga funksiya natijani echo bilan chiqaradi, chaqiruvchi esa uni command substitution bilan o'qiydi:
free_disk_percent() {
df --output=pcent / | tail -n 1 | tr -d '% '
}
percent=$(free_disk_percent)
echo "Free space: ${percent}%"
Ikkalasini bitta funksiyada aralashtirmang. Agar funksiya ham qiymat chiqarishi (echo), ham holat qaytarishi (return) kerak bo'lsa, odatda qiymatni echo orqali chiqarib, muvaffaqiyat/muvaffaqiyatsizlikni funksiyaning oxirgi buyrug'ining exit kodi orqali (yoki alohida, standart xatolar oqimiga — stderr ga yozilgan xabar orqali) ifodalash eng yaxshisi.
Funksiyalarni alohida faylga chiqarish
Bir nechta skript bir xil funksiyalardan foydalanishi mumkin (masalan, log yozuvchi yoki xato chiqaruvchi funksiya). Ularni har bir skriptda qayta yozish o'rniga alohida faylga chiqarib, kerakli skriptlarga "ulash" mumkin:
lib.sh:
backup.sh:
source (qisqa shakli: ., ya’ni . ./lib.sh) ko'rsatilgan faylni joriy shell kontekstida bajaradi. Ya’ni lib.sh ichidagi funksiyalar va o'zgaruvchilar xuddi backup.sh ga to'g'ridan-to'g'ri yozilgandek mavjud bo'ladi. Bu Backup skripti va boshqa loyiha skriptlarida umumiy yordamchi funksiyalarni takrorlamaslik uchun ishlatiladi.
Amaliy vaziyat: bir nechta servisni tekshiradigan funksiya
Vazifa: Shartlar va sikllar maqolasidagi bitta servisni tekshiradigan skriptni funksiya yordamida bir nechta servisni ketma-ket tekshiradigan qilib qayta yozish.
#!/bin/bash
check_service() {
local service="$1"
if systemctl is-active --quiet "$service"; then
echo "OK: $service is running"
return 0
else
echo "ERROR: $service is not running"
return 1
fi
}
failed=0
for service in nginx postgresql redis; do
if ! check_service "$service"; then
failed=$((failed + 1))
fi
done
if [ "$failed" -gt 0 ]; then
echo "$failed service(s) are not running"
exit 1
fi
echo "All services are running"
OK: nginx is running
ERROR: postgresql is not running
OK: redis is running
1 service(s) are not running
Bu yerda check_service bitta servisni tekshirish mantiqini bir marta tasvirlaydi, for sikli esa uni har bir servis nomi uchun qayta chaqiradi. Funksiyani sikl bilan birlashtirish kodni ancha qisqa va o'qilishi oson qiladi.
Keng tarqalgan xatolar
Funksiyani e’lon qilinishidan oldin chaqirish
Bash yuqoridan pastga o'qiydi. Agar funksiya hali aniqlanmagan bo'lsa, u haqida hech narsa bilmaydi. Yechim — funksiyalarni ular ishlatiladigan joydan oldin, skript boshiga joylashtirish.
local ni unutib, global o'zgaruvchini tasodifan qayta yozish
Agar ikkita funksiya bir xil nomli o'zgaruvchidan (masalan, i yoki result) local siz foydalansa, ular bir-birining qiymatini kutilmaganda o'zgartirib yuborishi mumkin. Bunday xatoni, ayniqsa, uzun skriptlarda topish qiyin.
return orqali matn qaytarishga urinish
return faqat 0–255 oralig'idagi butun sonni qabul qiladi. Matn uchun echo va command substitution’dan foydalaning (yuqoridagi free_disk_percent misoliga qarang).
Funksiya chiqishini (echo) uning exit kodi (return) bilan chalkashtirish
Bu noto'g'ri, chunki yuqoridagi misolda check_service echo orqali "OK: ..." xabarini chiqaradi, exit kodini esa return orqali qaytaradi. Exit kodni tekshirish uchun funksiyani to'g'ridan-to'g'ri shart ichida chaqiring: if check_service "nginx"; then ....
Mashqlar
- Argument sifatida berilgan satr nuqta bilan ajratilgan to'rtta sondan iboratligini oddiy tarzda tekshiradigan (murakkab CIDR yoki chegaraviy holatlarni ko'rib chiqish shart emas) va shunga mos ravishda
0yoki1qaytaradiganis_valid_ipnomli funksiya yozing. $1argumentini katta harflarga o'tkazib,echoorqali chiqaradiganto_uppernomli funksiya yozing (${1^^}parameter expansion’dan foydalanishingiz mumkin).- Yuqoridagi amaliy vaziyatdagi
check_servicefunksiyasinilib.shnomli alohida faylga chiqaring va uni asosiy skriptgasourcebilan ulang. - Biri
localsiz, ikkinchisilocalbilan yozilgan ikkita funksiyani taqqoslang: ikkalasida bir xil nomli o'zgaruvchidan foydalaning va natijadagi farqni kuzating.
Xulosa
Funksiyalar takrorlanadigan mantiqni bitta nom ostiga yig'adi, skriptni qisqartiradi va o'qishni osonlashtiradi. local o'zgaruvchini funksiya ichi bilan cheklab, tashqi holatga tasodifiy ta’sirning oldini oladi. return faqat exit kod uchun, qiymat qaytarish uchun esa echo command substitution bilan birga ishlatiladi. Keyingi mavzu, Exit kodlar va xatolarni qayta ishlash, aynan shu exit kodlar ortidagi to'liq mantiqni, xatoni oldindan aniqlashni va xato yuz berganda skriptni toza to'xtatishni ko'rib chiqadi.