Przejdź do treści
Linux i DevOps

Bash if, elif, else — instrukcje warunkowe w skryptach z przykładami

Jak działa if, elif i else w Bash? Składnia, różnica między [ ] a [[ ]], operatory porównań i testy plików, łączenie warunków oraz najczęstsze błędy w skryptach.

CZCzarek ZawolskiAktualizacja: 9 min czytania
Terminal Linux ze skryptem Bash zawierającym instrukcje warunkowe if, elif i else

W skrócie

  • Składnia: if WARUNEK; then … elif WARUNEK; then … else … fi — elif możesz powtórzyć dowolnie wiele razy, else jest opcjonalne.
  • if nie sprawdza „prawdy”, tylko kod wyjścia polecenia: 0 oznacza sukces (warunek spełniony), wszystko inne — niepowodzenie.
  • W Bash używaj [[ … ]] do tekstów i plików oraz (( … )) do liczb; [ … ] zostaw do skryptów, które muszą działać w czystym sh.
  • Wokół nawiasów kwadratowych obowiązkowe są spacje, a zmienne w [ … ] zawsze bierz w cudzysłów.
  • Przy wielu porównaniach jednej zmiennej z konkretnymi wartościami czytelniejsze od długiego łańcucha elif jest case.
Spis treści

W Bash instrukcja warunkowa ma postać if WARUNEK; then … elif INNY_WARUNEK; then … else … fi. Bash sprawdza warunki po kolei i wykonuje blok pierwszego spełnionego — elif (skrót od „else if”) dokłada kolejny warunek, a else wykonuje się, gdy żaden nie był spełniony. Całość zawsze zamyka słowo fi.

Najważniejsza rzecz, której nie mówi większość poradników: if w Bash nie ocenia „prawdy” jak w Pythonie czy JavaScripcie. Uruchamia polecenie i patrzy na jego kod wyjścia — 0 oznacza sukces, czyli warunek spełniony. Nawiasy [ ] to też polecenie. Kiedy to zrozumiesz, większość dziwnych błędów przestaje być zagadką.

Składnia if, elif i else w Bash

Pełna konstrukcja wygląda tak:

if WARUNEK_1; then
    polecenia gdy WARUNEK_1 spełniony
elif WARUNEK_2; then
    polecenia gdy WARUNEK_2 spełniony
else
    polecenia gdy żaden warunek nie jest spełniony
fi

Zasady, które warto zapamiętać:

  • then może być w tej samej linii po średniku albo w nowej linii — oba zapisy są poprawne,
  • bloków elif może być dowolnie wiele, każdy z własnym then,
  • else jest opcjonalny i występuje najwyżej raz, zawsze na końcu, bez then,
  • Bash wykonuje tylko pierwszy pasujący blok — kolejne warunki nie są już sprawdzane,
  • blok nie może być pusty; jeśli chcesz „nic nie robić”, wpisz : (polecenie, które zawsze kończy się sukcesem).

Najprostszy if i if-else

#!/usr/bin/env bash

if [[ -f /etc/os-release ]]; then
    echo "Plik /etc/os-release istnieje"
else
    echo "Brak pliku /etc/os-release"
fi

To samo w jednej linii, np. do wpisania prosto w terminalu:

if [[ -f /etc/os-release ]]; then echo "jest"; else echo "brak"; fi

Bash elif w praktyce: kilka warunków po kolei

Klasyczny przykład elif to klasyfikacja wartości według progów. Poniższy skrypt sprawdza zajętość partycji głównej i wypisuje odpowiedni komunikat:

#!/usr/bin/env bash

uzycie=$(df --output=pcent / | tail -1 | tr -dc '0-9')

if (( uzycie >= 95 )); then
    echo "KRYTYCZNIE: dysk zajęty w ${uzycie}%"
    exit 2
elif (( uzycie >= 80 )); then
    echo "OSTRZEŻENIE: dysk zajęty w ${uzycie}%"
    exit 1
else
    echo "OK: dysk zajęty w ${uzycie}%"
fi

Kolejność warunków ma znaczenie. Gdybyś najpierw sprawdził >= 80, a potem >= 95, drugi blok nigdy by się nie wykonał — przy 97% pierwszy warunek już jest spełniony. Przy progach zawsze zaczynaj od najbardziej restrykcyjnego.

Kody wyjścia 0, 1, 2 w przykładzie nie są przypadkowe — taką konwencję (OK, ostrzeżenie, błąd krytyczny) rozumieją systemy monitoringu, np. Nagios i jego następcy.

elif a else if — to nie to samo

elif dopisuje warunek do tego samego bloku. Zapis else if tworzy nowy, zagnieżdżony if, więc wymaga własnego fi:

if [[ $x -gt 10 ]]; then
    echo "większe niż 10"
else
    if [[ $x -gt 5 ]]; then
        echo "od 6 do 10"
    fi
fi

Działa, ale jest mniej czytelne. Zapomniane drugie fi kończy się błędem syntax error: unexpected end of file — to jedna z najczęstszych pomyłek osób przychodzących z C czy Javy.

[ ], [[ ]] i (( )) — czym sprawdzać warunki

W Bash masz trzy główne sposoby zapisu warunku. Każdy ma swoje zastosowanie.

ZapisCo to jestDo czego używać
[ … ]Polecenie test (zgodne z POSIX)Skrypty, które muszą działać także w sh, dash, BusyBox
[[ … ]]Słowo kluczowe BashTeksty, pliki, wzorce i wyrażenia regularne w skryptach Bash
(( … ))Arytmetyka BashPorównania liczb całkowitych, z normalnymi operatorami <, >=, ==
samo polecenieDowolny programSprawdzanie, czy polecenie się udało, np. grep -q, ping -c1

Dlaczego [[ ]] jest bezpieczniejsze od [ ]? W [[ ]] Bash nie dzieli zmiennych na słowa i nie rozwija w nich znaków *, więc pusta lub zawierająca spacje zmienna nie psuje testu. Obsługuje też &&, ||, wzorce i wyrażenia regularne. Jeśli jednak skrypt zaczyna się od #!/bin/sh, a na Debianie czy Ubuntu sh to dash, [[ skończy się błędem [[: not found.

Operatory porównań i testy plików

Porównywanie liczb

Operator w [ ] / [[ ]]W (( ))Znaczenie
-eq==równe
-ne!=różne
-lt<mniejsze
-le<=mniejsze lub równe
-gt>większe
-ge>=większe lub równe

Bash liczy tylko na liczbach całkowitych. Porównanie [[ 2.5 -gt 2 ]] skończy się błędem — do ułamków użyj bc lub awk.

Porównywanie tekstów

TestZnaczenie
[[ $a == $b ]] lub [ "$a" = "$b" ]teksty równe
[[ $a != $b ]]teksty różne
[[ -z $a ]]tekst pusty (długość zero)
[[ -n $a ]]tekst niepusty
[[ $plik == *.log ]]dopasowanie do wzorca (tylko [[ ]], prawa strona bez cudzysłowu)
[[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]dopasowanie do wyrażenia regularnego (tylko [[ ]])

Uwaga na pułapkę: w [ ] znaki < i > są przekierowaniami. [ "$a" > "$b" ] nie porówna tekstów, tylko utworzy plik o nazwie z wartością $b.

Testy plików

TestPrawda, gdy…
-e plikistnieje (dowolny typ)
-f plikistnieje i jest zwykłym plikiem
-d plikistnieje i jest katalogiem
-L plikjest dowiązaniem symbolicznym
-s plikistnieje i ma rozmiar większy od zera
-r / -w / -x plikmasz prawo odczytu / zapisu / wykonania
plik1 -nt plik2plik1 jest nowszy od plik2

Pełną listę znajdziesz w podręczniku GNU Bash w sekcji o wyrażeniach warunkowych albo lokalnie poleceniem help test.

Łączenie warunków: AND, OR i negacja

W [[ ]] operatory logiczne piszesz wewnątrz nawiasów:

if [[ $wiek -ge 18 && $wiek -lt 65 ]]; then
    echo "wiek produkcyjny"
fi

if [[ $srodowisko == "prod" || $srodowisko == "staging" ]]; then
    echo "Uwaga: środowisko współdzielone"
fi

if [[ ! -d /var/backup ]]; then
    mkdir -p /var/backup
fi

W [ ] łączysz osobne testy operatorami powłoki: [ "$a" -gt 0 ] && [ "$a" -lt 10 ]. Starsze opcje -a i -o wewnątrz [ ] są w standardzie POSIX oznaczone jako przestarzałe — lepiej ich unikać.

Nawiasy grupujące w [[ ]] działają normalnie: [[ ( $a -gt 5 && $a -lt 10 ) || $a -eq 0 ]].

Warunek jako polecenie: grep, command, ping

Skoro if sprawdza kod wyjścia, warunkiem może być dowolny program. To bardzo częsty wzorzec w skryptach administracyjnych:

if grep -q "^PermitRootLogin yes" /etc/ssh/sshd_config; then
    echo "Logowanie roota przez SSH jest włączone"
fi

if ! command -v docker >/dev/null 2>&1; then
    echo "Docker nie jest zainstalowany" >&2
    exit 1
fi

if systemctl is-active --quiet nginx; then
    echo "nginx działa"
elif systemctl is-enabled --quiet nginx; then
    echo "nginx jest włączony, ale nie działa — restartuję"
    systemctl restart nginx
else
    echo "nginx wyłączony"
fi

Opcja -q w grep i --quiet w systemctl wyciszają wynik — interesuje nas tylko kod wyjścia. Więcej o zarządzaniu usługami przeczytasz w poradniku systemctl — do czego służy.

Skrót && i || — i jego pułapka

Proste warunki często zapisuje się bez if:

[[ -d /var/backup ]] || mkdir -p /var/backup

Konstrukcja warunek && A || B nie jest jednak pełnym odpowiednikiem if-else. B wykona się także wtedy, gdy warunek był spełniony, ale A zakończyło się błędem. Przy czymkolwiek ważniejszym niż echo pisz normalne if.

case — lepsza alternatywa dla długiego łańcucha elif

Gdy porównujesz jedną zmienną z wieloma konkretnymi wartościami, case jest czytelniejszy niż pięć bloków elif:

case "$1" in
    start)
        echo "Uruchamiam" ;;
    stop)
        echo "Zatrzymuję" ;;
    restart|reload)
        echo "Restartuję" ;;
    *)
        echo "Użycie: $0 {start|stop|restart}" >&2
        exit 1 ;;
esac

elif zostaw na sytuacje, w których warunki są różnego typu (np. najpierw test pliku, potem porównanie liczby) albo sprawdzasz zakresy.

Najczęstsze błędy w instrukcjach warunkowych Bash

Komunikat lub objawPrzyczynaPoprawka
[: missing ']' lub command not found przy [brak spacji wokół nawiasów, np. [$a -gt 5][ "$a" -gt 5 ] — spacja po [ i przed ]
unary operator expectedpusta zmienna bez cudzysłowu w [ ][ "$a" = "tak" ] albo [[ $a == tak ]]
integer expression expectedporównanie liczbowe na tekście lub ułamkusprawdź zawartość zmiennej, do ułamków użyj bc
syntax error near unexpected token 'elif'brak then po poprzednim warunku albo brak średnikaif [[ … ]]; then
unexpected end of filebrakujące fi (często przy else if)zamknij każdy if własnym fi lub użyj elif
[[: not foundskrypt uruchamiany przez sh/dashshebang #!/usr/bin/env bash i uruchamianie przez bash skrypt.sh
warunek zawsze prawdziwyif [ $a=5 ] bez spacji przy = — test dostaje jeden niepusty tekst[ "$a" = 5 ] ze spacjami

Wskazówka: Zainstaluj ShellCheck (sudo apt install shellcheck lub sudo dnf install ShellCheck) i uruchamiaj shellcheck skrypt.sh przed wdrożeniem. Wyłapuje brakujące cudzysłowy, pomylone operatory i większość błędów z tabeli powyżej.

Kompletny przykład: skrypt kopii zapasowej z walidacją argumentów

Na koniec skrypt, który łączy wszystkie omówione elementy — sprawdzanie argumentów, testy plików, elif i warunek oparty na poleceniu:

#!/usr/bin/env bash
set -euo pipefail

zrodlo="${1:-}"
cel="${2:-/var/backup}"

if [[ -z $zrodlo ]]; then
    echo "Użycie: $0 KATALOG_ŹRÓDŁOWY [KATALOG_DOCELOWY]" >&2
    exit 1
elif [[ ! -d $zrodlo ]]; then
    echo "Błąd: $zrodlo nie jest katalogiem" >&2
    exit 1
elif [[ ! -r $zrodlo ]]; then
    echo "Błąd: brak prawa odczytu do $zrodlo" >&2
    exit 1
fi

[[ -d $cel ]] || mkdir -p "$cel"

archiwum="$cel/backup-$(date +%F).tar.gz"

if tar -czf "$archiwum" -C "$(dirname "$zrodlo")" "$(basename "$zrodlo")"; then
    echo "Kopia zapisana: $archiwum"
else
    echo "Tworzenie kopii nie powiodło się" >&2
    exit 1
fi

Zapis ${1:-} podstawia pusty tekst, gdy argument nie został podany — bez tego set -u przerwałby skrypt przed naszym czytelnym komunikatem. Jeśli dopiero zaczynasz pracę z terminalem, przyda Ci się też zestawienie 20 poleceń Linux, które musisz znać, a gdy skryptów zacznie przybywać i obsługujesz wiele serwerów — poradnik Ansible.

Najczęściej zadawane pytania

Czym różni się elif od else if w Bash?

elif to jedna instrukcja, która dokłada kolejny warunek do tego samego bloku if i kończy się jednym fi. Zapis else if tworzy nowy, zagnieżdżony if, więc wymaga dodatkowego fi na końcu.

Czy w Bash po elif trzeba pisać then?

Tak. Każdy warunek — zarówno po if, jak i po elif — musi być zakończony słowem then, np. elif [[ $x -gt 5 ]]; then. Po else nie piszesz then.

Jak sprawdzić w Bash, czy plik istnieje?

Użyj if [[ -f /sciezka/plik ]]; then … fi dla zwykłego pliku, -d dla katalogu albo -e, gdy wystarczy dowolny obiekt o tej nazwie.

Skąd błąd „unary operator expected”?

Najczęściej zmienna w [ … ] jest pusta i nie jest ujęta w cudzysłów, więc test widzi za mało argumentów. Pisz [ "$zmienna" = "tak" ] albo używaj [[ … ]].

Jak zapisać if w jednej linii w Bash?

Oddziel części średnikami: if [[ -d /backup ]]; then echo ok; else echo brak; fi. Średnik jest potrzebny przed then, else i fi.

CZ

Autor

Czarek Zawolski

Założyciel i redaktor XAD.pl. Pisze o sieciach, bezpieczeństwie IT, administracji systemami Windows i Linux oraz o sprzęcie, który sprawia ludziom problemy na co dzień.