Tarkibga o'tish

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;
  • local kalit so'zi bilan o'zgaruvchining amal qilish doirasini (scope) cheklash;
  • funksiya ichida natijani return bilan qaytarish va echo bilan 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:

greet() {
    echo "Hello, Linux!"
}
function greet {
    echo "Hello, Linux!"
}

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:

#!/bin/bash

greet() {
    echo "Hello, Linux!"
}

greet
Hello, Linux!

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:

#!/bin/bash

greet() {
    echo "Hello, $1!"
}

greet "World"
greet "Ali"
Hello, World!
Hello, Ali!

$@ 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
15

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"
Inside the function: 11
After the function: 11

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"
Inside the function: 1
After the function: 10

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:

get_timestamp() {
    local ts=$(date +%s)   # looks fine...
    echo "$ts"
}

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:

get_timestamp() {
    local ts
    ts=$(date +%s)         # exit status of date is now preserved
    echo "$ts"
}

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}%"
Free space: 42%

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:

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

backup.sh:

#!/bin/bash

source ./lib.sh

log "Backup started"
[2026-07-26 14:03:12] Backup started

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

greet
greet() {
    echo "Hello!"
}
bash: greet: command not found

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

get_name() {
    return "Ali"
}
bash: return: Ali: numeric argument required

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

result=$(check_service "nginx")
if [ "$result" = "0" ]; then

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

  1. 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 0 yoki 1 qaytaradigan is_valid_ip nomli funksiya yozing.
  2. $1 argumentini katta harflarga o'tkazib, echo orqali chiqaradigan to_upper nomli funksiya yozing (${1^^} parameter expansion’dan foydalanishingiz mumkin).
  3. Yuqoridagi amaliy vaziyatdagi check_service funksiyasini lib.sh nomli alohida faylga chiqaring va uni asosiy skriptga source bilan ulang.
  4. Biri local siz, ikkinchisi local bilan 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.

Manbalar