Bash skripte 3 — uslovi i petlje

U drugom delu skripta je počela da radi sa različitim ulazima. Sada je učimo da donosi odluke i da ponavlja posao — bez toga skripta ostaje spisak komandi.

Sve počinje od izlaznog koda

Bash nema pravi tip „tačno/netačno". Umesto toga gleda izlazni kod komande: nula znači uspeh, sve ostalo neuspeh.

$ ls /etc > /dev/null
$ echo $?
0

$ ls /nepostojeci 2> /dev/null
$ echo $?
2

Zato se if u bash-u ne pita „da li je ovo tačno" nego „da li je ova komanda uspela". Kad to jednom shvatiš, sve ostalo ima smisla.

if

#!/usr/bin/env bash

if [ -f /etc/passwd ]; then
    echo "Fajl postoji"
fi

Puni oblik:

if uslov; then
    komande
elif drugi_uslov; then
    komande
else
    komande
fi

Blok se zatvara sa fiif unazad.

Razmaci unutar uglastih zagrada su obavezni

if [ -f "$fajl" ]; then     # radi
if [-f "$fajl"]; then       # ne radi

Razlog je što je [ zapravo komanda, a ne deo sintakse. Bez razmaka bash traži komandu koja se zove [-f.

Provera fajlova

Ovo su operatori koje ćeš kao administrator koristiti najčešće:

OperatorTačno ako
-e putanjapostoji (bilo šta)
-f putanjapostoji i običan je fajl
-d putanjapostoji i direktorijum je
-L putanjasimbolički link
-s putanjapostoji i nije prazan
-r putanjaimaš pravo čitanja
-w putanjaimaš pravo pisanja
-x putanjaizvršno je
#!/usr/bin/env bash

konfig="/etc/httpd/conf/httpd.conf"

if [ ! -f "$konfig" ]; then
    echo "Konfiguracija nije pronađena: $konfig"
    exit 1
fi

if [ ! -r "$konfig" ]; then
    echo "Nemam pravo čitanja. Pokreni kao root."
    exit 1
fi

echo "Konfiguracija je u redu"

Uzvičnik obrće uslov. Ovakav „izađi rano ako nešto ne valja" obrazac je osnova pouzdane skripte.

Razlika između -e i -f ume da zbuni: -e je tačno i za direktorijum, uređaj ili soket, a -f samo za običan fajl. Kad proveravaš da li fajl postoji pre čitanja, hoćeš -f.

Poređenje brojeva

OperatorZnačenje
-eqjednako
-nerazličito
-ltmanje od
-lemanje ili jednako
-gtveće od
-geveće ili jednako
#!/usr/bin/env bash

zauzeto=$(df / | tail -1 | awk '{print $5}' | tr -d '%')

if [ "$zauzeto" -gt 90 ]; then
    echo "UPOZORENJE: root particija je $zauzeto% puna"
    exit 1
elif [ "$zauzeto" -gt 75 ]; then
    echo "Pažnja: $zauzeto% zauzeto"
else
    echo "Disk u redu: $zauzeto%"
fi

Ovo je gotova skripta za nadzor — zakači je na cron i dobijaš upozorenje pre nego što ti se disk napuni.

Poređenje teksta

OperatorZnačenje
=isti tekst
!=različit tekst
-zprazno
-nnije prazno
if [ "$1" = "start" ]; then
    echo "Pokrećem servis"
fi

if [ -z "$1" ]; then
    echo "Upotreba: $0 [start|stop]"
    exit 1
fi

Pazi: za brojeve koristi -eq, za tekst =. Zamena mesta daje tiho pogrešne rezultate — poređenje "10" < "9" je tačno ako se gleda kao tekst.

Navodnici ovde nisu opcioni

fajl=""
if [ $fajl = "test" ]; then    # greška u sintaksi
if [ "$fajl" = "test" ]; then  # radi

Bez navodnika prazna promenljiva nestaje i uslov ostane bez leve strane.

Više uslova

if [ -f "$fajl" ] && [ -r "$fajl" ]; then
    echo "Postoji i mogu da ga čitam"
fi

if [ "$odgovor" = "d" ] || [ "$odgovor" = "D" ]; then
    echo "Nastavljam"
fi

Dvostruke zagrade

Bash ima i noviji oblik [[ ]], koji je udobniji:

if [[ -f "$fajl" && -r "$fajl" ]]; then
    echo "U redu"
fi

if [[ "$ime" == alen* ]]; then
    echo "Ime počinje sa 'alen'"
fi

if [[ "$fajl" =~ \.log$ ]]; then
    echo "Ovo je log fajl"
fi

Unutar [[ ]] promenljive se ne moraju štititi navodnicima, radi poređenje šablona sa == i regularni izrazi sa =~.

Jedina mana: [[ ]] je bash proširenje i ne radi u čistom sh. Ako je tvoj shebang #!/usr/bin/env bash — a jeste — slobodno ga koristi.

Uslov nad komandom

Uglaste zagrade nisu obavezne. Bilo koja komanda može biti uslov:

if systemctl is-active --quiet httpd; then
    echo "Apache radi"
else
    echo "Apache ne radi — pokrećem ga"
    systemctl start httpd
fi
if grep -q "error" /var/log/httpd/error_log; then
    echo "Ima grešaka u logu"
fi

Opcija -q kod grep-a ućutkuje ispis — zanima nas samo izlazni kod. Ovo je čest i vrlo čitljiv obrazac.

&& i || kao prečica

mkdir -p /backup && echo "Direktorijum spreman"
systemctl start httpd || echo "Pokretanje nije uspelo"

Druga komanda se izvršava samo ako je prva uspela (&&), odnosno samo ako nije (||). Zgodno za kratke provere; za išta duže koristi pun if.

case

Kad ima više mogućnosti, case je čitljiviji od gomile elif-ova:

#!/usr/bin/env bash

case "$1" in
    start)
        echo "Pokrećem"
        systemctl start httpd
        ;;
    stop)
        echo "Zaustavljam"
        systemctl stop httpd
        ;;
    restart)
        echo "Restartujem"
        systemctl restart httpd
        ;;
    status)
        systemctl status httpd
        ;;
    *)
        echo "Upotreba: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

Svaka grana se zatvara sa ;;, a *) hvata sve ostalo. Blok se zatvara sa esac.

Grane prihvataju i šablone:

case "$fajl" in
    *.tar.gz|*.tgz)  tar -xzf "$fajl" ;;
    *.zip)           unzip "$fajl" ;;
    *.gz)            gunzip "$fajl" ;;
    *)               echo "Nepoznat format" ;;
esac

for petlja

Kroz spisak

for servis in httpd sshd crond; do
    if systemctl is-active --quiet "$servis"; then
        echo "$servis: radi"
    else
        echo "$servis: NE RADI"
    fi
done

Kroz fajlove

for fajl in /var/log/*.log; do
    echo "$fajl — $(wc -l < "$fajl") redova"
done

Bash sam proširuje *.log u spisak fajlova. Ako nijedan ne odgovara šablonu, petlja se izvrši jednom sa doslovnim /var/log/*.log — zato unutra proveri [ -f "$fajl" ] kad to može da se desi.

Kroz argumente skripte

for arg in "$@"; do
    echo "Obrađujem: $arg"
done

Kroz opseg brojeva

for i in {1..5}; do
    echo "Pokušaj $i"
done

for i in {0..20..5}; do
    echo "$i"
done

I klasični oblik, kad granica dolazi iz promenljive:

for (( i=1; i<=5; i++ )); do
    echo "Broj $i"
done

Vitičaste zagrade ne rade sa promenljivama — {1..$n} se ne proširuje. U tom slučaju koristi oblik sa dvostrukim zagradama ili seq.

Kroz izlaz komande

for korisnik in $(cut -d: -f1 /etc/passwd); do
    echo "$korisnik"
done

Oprez: ovo puca ako neka vrednost sadrži razmak. Za obradu redova iz fajla bolji je while read, odmah ispod.

while petlja

Ponavlja se dok je uslov ispunjen:

brojac=1

while [ "$brojac" -le 5 ]; do
    echo "Pokušaj $brojac"
    brojac=$((brojac + 1))
done

Čitanje fajla red po red

Ovo je najkorisniji oblik while petlje:

while read -r linija; do
    echo "Red: $linija"
done < /etc/hosts

Opcija -r sprečava da bash tumači obrnute kose crte — piši je uvek.

Sa razdvajanjem polja:

while IFS=: read -r korisnik _ uid _ _ home _; do
    if [ "$uid" -ge 1000 ]; then
        echo "$korisnik ($uid) — $home"
    fi
done < /etc/passwd

Promenljiva IFS određuje razdvajač polja. Donja crta se po dogovoru koristi za kolone koje te ne zanimaju.

Čekanje na uslov

until systemctl is-active --quiet httpd; do
    echo "Čekam da se Apache podigne..."
    sleep 2
done

echo "Apache je pokrenut"

until je obrnut while — vrti se dok uslov nije ispunjen.

break i continue

for fajl in /var/log/*.log; do
    if [ ! -r "$fajl" ]; then
        continue        # preskoči ovaj, idi na sledeći
    fi

    if grep -q "FATAL" "$fajl"; then
        echo "Fatalna greška u $fajl"
        break           # prekini petlju
    fi
done

Sve zajedno: provera stanja servera

#!/usr/bin/env bash
# Provera osnovnog stanja servera
# Upotreba: ./provera.sh [prag_diska]

prag="${1:-85}"
greske=0

echo "=== Provera servisa ==="
for servis in httpd sshd crond; do
    if systemctl is-active --quiet "$servis"; then
        echo "  $servis: radi"
    else
        echo "  $servis: NE RADI"
        greske=$((greske + 1))
    fi
done

echo
echo "=== Provera diskova ==="
while read -r particija zauzeto; do
    zauzeto="${zauzeto%\%}"

    if [ "$zauzeto" -ge "$prag" ]; then
        echo "  $particija: $zauzeto% — PREKO PRAGA"
        greske=$((greske + 1))
    else
        echo "  $particija: $zauzeto%"
    fi
done < <(df -h --output=target,pcent | tail -n +2)

echo
if [ "$greske" -eq 0 ]; then
    echo "Sve u redu."
    exit 0
else
    echo "Pronađeno problema: $greske"
    exit 1
fi

Zapis < <(komanda) prosleđuje izlaz komande u petlju kao da je fajl. Deluje čudno, ali je bitan: da si napisao df ... | while read, petlja bi se izvršila u zasebnom procesu i promenljiva greske bi na kraju bila nula.

Skripta vraća 1 ako ima problema, pa je alat za nadzor ili cron odmah prepoznaje kao neuspeh.

Vežba

Napiši skriptu koja prima direktorijum kao argument i:

  • proverava da direktorijum postoji, inače izlazi sa porukom
  • prolazi kroz sve fajlove u njemu
  • za svaki ispisuje ime, veličinu i da li je stariji od 30 dana
  • na kraju ispisuje ukupan broj fajlova i koliko ih je starih

Trebaće ti find sa opcijom -mtime ili operator -nt za poređenje datuma fajlova.


Sledeći deo: funkcije, obrada grešaka sa set -euo i kompletna backup skripta sa rotacijom starih arhiva.

Povezano: Bash skripte 2 — promenljive i argumenti · grep · systemctl · Programiranje

Comments

Popular posts from this blog

Početak u Linuxu — šta je i zašto se uči

Konverzija tipova podataka u Pythonu

groupadd