Funkcje
Po tej lekcji: kursant zamyka powtarzalne kawałki skryptu w funkcjach, przekazuje im argumenty, chroni zmienne przez local i wie, kiedy funkcja ma „oddać” wynik przez echo, a kiedy przez return.
Wstęp
Skrypt, który odziedziczyłeś po poprzednim adminie. W czterdziestu miejscach jest linijka wypisująca komunikat z datą. Szef mówi: „Dopisz do każdego komunikatu nazwę serwera”. Czterdzieści poprawek. W trzydziestu dziewięciu zrobisz dobrze, a w jednej się pomylisz i nikt tego nie zauważy przez pół roku.
A gdyby ta linijka była w jednym miejscu, a w czterdziestu pozostałych tylko się do niej odwoływać? Jedna poprawka, czterdzieści efektów. To są funkcje. Ostatnia lekcja tego modułu i ostatni klocek, którego brakowało, żeby pisać prawdziwe narzędzia.
Funkcja, czyli przycisk w windzie
Wyobraź sobie windę. Wciskasz „4” i winda sama robi całą procedurę: zamyka drzwi, rusza, hamuje na czwartym, otwiera. Nie musisz wiedzieć, jak to działa w środku. Ważne, że przycisk jest jeden, a procedurę ktoś opisał raz, porządnie.
Funkcja to właśnie taki przycisk. Nazwa plus kawałek kodu, który się wykona, kiedy użyjesz tej nazwy jak komendy.
#!/bin/bash
# f1.sh — pierwsze funkcje
powitanie() {
echo "Cześć, $1! Dostałem argumentów: $#"
}
log() {
local poziom=$1
shift
echo "$(date '+%F %T') [$poziom] $*"
}
powitanie Eryk
powitanie "Anna Nowak" drugi trzeci
log INFO "Zaczynam kopię"
log BLAD "Brak miejsca na dysku"
echo "A w skrypcie \$1 to nadal: '$1'"./f1.sh skryptowyCześć, Eryk! Dostałem argumentów: 1
Cześć, Anna Nowak! Dostałem argumentów: 3
2026-11-03 16:40:18 [INFO] Zaczynam kopię
2026-11-03 16:40:18 [BLAD] Brak miejsca na dysku
A w skrypcie $1 to nadal: 'skryptowy'Rozbieramy.
powitanie() { ... } to definicja. Nazwa, pusta para nawiasów i w klamrach to, co ma się dziać. Nawiasy są zawsze puste. Nie wpisujesz tam argumentów, jak w innych językach. To tylko znak dla Basha: „to jest funkcja”.
Wywołanie wygląda dokładnie jak zwykła komenda: powitanie Eryk. Bez nawiasów, bez przecinków. Dla Basha Twoja funkcja to po prostu kolejna komenda w okienku.
Argumenty? Tak samo jak w skrypcie. W środku funkcji $1 to pierwszy argument funkcji, $# to ich liczba, "$@" to wszystkie. Wszystko, co poznałeś przy argumentach skryptu, działa tu jeden do jednego. Nawet shift, które w funkcji log zdejmuje pierwszy argument, żeby resztę wypisać jako treść komunikatu.
I ważne: funkcja ma swoje własne $1. Skrypt dostał skryptowy, funkcja dostała Eryk, i nic się nie pomieszało. Ostatnia linijka to pokazuje. Każdy przycisk w windzie ma własne kabelki.
Teraz hak z czterdziestoma linijkami załatwiasz tak: dopisujesz $(hostname) w jednym miejscu, w funkcji log. Gotowe. Możesz iść na kawę, którą właśnie zaoszczędziłeś.
Funkcja w ~/.bashrc, czyli alias na sterydach
Pamiętasz aliasy z lekcji o ustawianiu Basha pod siebie? Spróbujmy aliasem zrobić komendę „załóż folder i od razu do niego wejdź”:
alias mkcd='mkdir -p $1 && cd $1'
mkcd projektmkdir: missing operand
Try 'mkdir --help' for more information.Alias nie umie przyjmować argumentów. To tylko podmiana tekstu: Bash wkleja treść aliasu, a projekt dokleja na samym końcu. $1 w aliasie nic nie znaczy. Funkcja umie:
mkcd() {
mkdir -p "$1" && cd "$1" || return
}Wrzuć to do ~/.bashrc, przeczytaj plik ponownie przez source ~/.bashrc i od teraz mkcd /tmp/nowy/projekt zakłada całe drzewo folderów i od razu Cię tam przenosi. Tu widać jeszcze jedną przewagę: funkcja działa w Twojej powłoce, więc jej cd zmienia Twój folder. Skrypt uruchomiony przez ./skrypt.sh tego nie potrafi, bo działa w osobnym procesie.
Sprawdź, co Bash wie o funkcji:
type mkcdmkcd is a function
mkcd ()
{
mkdir -p "$1" && cd "$1" || return
}A unset -f mkcd usuwa funkcję z bieżącej sesji.
return kontra echo, czyli kciuk i kartka
To jest miejsce, w którym mylą się wszyscy, którzy przyszli z innych języków programowania. W Pythonie czy JavaScripcie return oddaje wynik. W Bashu nie.
Funkcja w Bashu może oddać dwie rzeczy, tak jak każda komenda. Kciuk, czyli kod wyjścia od 0 do 255. I kartkę z tekstem, czyli to, co wypisze na pierwszy głośnik. return ustawia kciuk. echo pisze na kartce.
#!/bin/bash
# f2.sh — return kontra echo
czy_root() {
[ "$(id -u)" -eq 0 ]
}
rozmiar() {
du -sh "$1" | cut -f1
}
if czy_root; then
echo "Jestem rootem"
else
echo "Nie jestem rootem"
fi
r=$(rozmiar /etc)
echo "Folder /etc zajmuje: $r"Jestem rootem
Folder /etc zajmuje: 18Mczy_root to pytanie typu tak albo nie. Nie ma w niej nawet słowa return. Jeśli go nie napiszesz, funkcja oddaje kciuk ostatniej komendy, którą wykonała. Ostatnią komendą jest bramkarz [ ], więc jego kciuk staje się kciukiem funkcji. Dzięki temu funkcję wstawiasz prosto w if, jak grep -q czy test.
rozmiar to pytanie o wartość. Wynik wypisuje na ekran, a ten, kto pyta, łapie go gońcem $( ) z poprzedniej lekcji.
Zasada na kartkę przy monitorze: tak albo nie, czyli sukces albo porażka, idzie przez return albo kciuk ostatniej komendy. Wszystko inne, czyli liczby, nazwy, ścieżki, teksty, idzie przez echo i $( ).
A co się stanie, jeśli spróbujesz zwrócić przez return prawdziwą liczbę?
za_duzo() {
return 300
}
za_duzo
echo "kod: $?"kod: 44Trzysta zamieniło się w czterdzieści cztery, bo kod wyjścia mieści tylko liczby od 0 do 255 i reszta się „przekręca”, jak licznik kilometrów w starym maluchu. ShellCheck od razu krzyczy: Can only return 0-255. Other data should be written to stdout. A return abc kończy się komunikatem numeric argument required. Kciuk nie umie pokazać słowa.
local, czyli własne pudełka funkcji
Zmienne w Bashu są domyślnie wspólne dla całego skryptu. Funkcja, która użyje zmiennej o tej samej nazwie co skrypt, po cichu ją nadpisze. Jak wspólna lodówka w biurze: ktoś włożył jogurt podpisany „plik”, a ktoś inny, nie patrząc, włożył tam swój i zabrał tamten.
#!/bin/bash
# zasieg.sh — funkcja bez local psuje zmienną skryptu
plik="raport.txt"
policz_logi() {
for plik in logi/*.log; do
:
done
echo "Ostatni log: $plik"
}
policz_logi
echo "Zapiszę raport do: $plik"Ostatni log: logi/web.log
Zapiszę raport do: logi/web.logSkrypt chciał zapisać raport do raport.txt, a teraz zapisze go do logi/web.log. Czyli nadpisze log aplikacji raportem. Taki błąd wychodzi dopiero na produkcji, oczywiście w piątek.
(Dwukropek w pętli to komenda „nic nie rób”. Tu tylko udaje prawdziwą robotę.)
Naprawa to jedna linijka na początku funkcji:
policz_logi() {
local plik
for plik in logi/*.log; do
:
done
echo "Ostatni log: $plik"
}Ostatni log: logi/web.log
Zapiszę raport do: raport.txtlocal plik mówi: „na czas działania tej funkcji mam swoje własne pudełko o tej nazwie”. Funkcja bawi się swoim, a pudełko skryptu leży nietknięte. Kiedy funkcja się kończy, jej pudełko znika, a nazwa wraca do poprzedniej wartości.
Zasada: każda zmienna, której używasz tylko w funkcji, dostaje local. Bez wyjątków. Tak jak w biurowej lodówce: podpisuj jogurty.
Jak to działa pod spodem
Kiedy Bash czyta definicję funkcji, nie wykonuje jej. Tylko zapamiętuje: „pod nazwą log jest taki kawałek kodu”. Dopiero kiedy ktoś wpisze log, urzędnik sprawdza w kolejności: czy to alias, czy to słowo kluczowe, czy to funkcja, czy to wbudowana komenda, a na samym końcu szuka programu w folderach z PATH. Funkcje są przed programami. Dlatego funkcja o nazwie ls przykryje prawdziwe ls. Możesz się tak zabawić z kolegą, ale nie zdziw się, jak kolegą przestanie być.
Funkcja nie uruchamia nowego procesu. Wykonuje się w tej samej powłoce co skrypt, przez tego samego kucharza. Dlatego jest szybka, dlatego może zmienić folder przez cd i dlatego bez local może popsuć zmienne skryptu. To wszystko jedna i ta sama cecha.
Co robi Bash przy wywołaniu? Odkłada na bok argumenty skryptu, podstawia na ich miejsce argumenty funkcji, wykonuje jej ciało, a na końcu przywraca argumenty skryptu. Przy local robi to samo ze zmiennymi: zapamiętuje starą wartość i przywraca ją przy wyjściu z funkcji.
Ściąga flag
| Komenda | Flaga / element | Co robi |
|---|---|---|
| definicja | nazwa() { ...; } | tworzy funkcję |
| definicja | function nazwa { ...; } | to samo, składnia tylko Basha |
| argumenty | $1, $#, "$@" | argumenty funkcji, nie skryptu |
local | local x=5 | zmienna widoczna tylko w funkcji |
return | return 0 / return 1 | kończy funkcję z kodem 0–255 |
return | return bez liczby | oddaje kod ostatniej komendy |
echo | echo "$wynik" | oddaje wynik do złapania przez $( ) |
echo | echo "..." >&2 | komunikat na drugi głośnik, nie do wyniku |
type | type nazwa | czy to funkcja, alias, czy program, i jaka treść |
declare | -F / -f | lista funkcji / ich treść |
unset | -f nazwa | usuwa funkcję |
⚠ Typowy błąd
Funkcja ma zwrócić wolne miejsce na dysku. Dla wygody dopisujesz komunikat, co robi:
#!/bin/bash
# debug.sh — komunikat diagnostyczny wpada do wyniku
wolne_miejsce() {
echo "Sprawdzam dysk $1..."
df --output=avail -h "$1" | tail -n 1
}
wolne=$(wolne_miejsce /)
echo "Wolne: [$wolne]"Wolne: [Sprawdzam dysk /...
26G]W zmiennej wylądowało wszystko, co funkcja wypisała. Także Twoja uprzejma informacja. Goniec $( ) nie odróżnia wyniku od gadania, zabiera całą kartkę. Jeśli potem porównasz to z liczbą albo użyjesz jako nazwy pliku, skrypt się wyłoży w miejscu, które z funkcją nie ma nic wspólnego.
Naprawa: komunikaty dla człowieka na drugi głośnik.
wolne_miejsce() {
echo "Sprawdzam dysk $1..." >&2
df --output=avail -h "$1" | tail -n 1
}Sprawdzam dysk /...
Wolne: [ 26G]Komunikat dalej widać na ekranie, ale do zmiennej trafił tylko wynik. >&2 to dokładnie ten sam drugi głośnik, który poznaliśmy przy przekierowaniach: wyniki pierwszym, gadanie i błędy drugim.
I drugi, krótki: funkcja musi być zdefiniowana, zanim jej użyjesz. Bash czyta skrypt od góry i wywołanie przed definicją kończy się tak:
kolej.sh: line 2: przywitaj: command not foundDlatego w porządnym skrypcie wszystkie funkcje stoją na górze, a właściwa robota na dole. ShellCheck to wyłapie: This function is only defined later. Move the definition up.
✎ Zadanie
Napisz funkcję kopia, która dostaje nazwę pliku i robi jego kopię z dzisiejszą datą w nazwie (np. notatki.txt.2026-11-03). Jeśli plik nie istnieje, wypisuje błąd i zwraca 1.
Pokaż rozwiązanie
Zadanie było takie: funkcja kopia, która dostaje nazwę pliku i robi jego kopię z dzisiejszą datą w nazwie. A jak pliku nie ma, krzyczy i zwraca 1.
#!/bin/bash
# kopia PLIK – robi kopię pliku z dzisiejszą datą w nazwie
kopia() {
local plik=$1
local cel
cel="$plik.$(date +%F)"
if [[ ! -f $plik ]]; then
echo "kopia: nie ma takiego pliku: '$plik'" >&2
return 1
fi
cp -p -- "$plik" "$cel"
echo "Zrobione: $cel"
}Sprawdzamy. Wczytuję plik z funkcją przez source i naciskam przycisk w windzie:
source ./kopia.sh
kopia notatki.txt; echo "kod: $?"
kopia nie_ma.txt; echo "kod: $?"
kopia; echo "kod: $?"Zrobione: notatki.txt.2026-11-03
kod: 0
kopia: nie ma takiego pliku: 'nie_ma.txt'
kod: 1
kopia: nie ma takiego pliku: ''
kod: 1Kilka rzeczy wartych uwagi. date +%F daje datę w formacie rok-miesiąc-dzień, czyli taką, która sortuje się sama. local, żeby funkcja nie nabałaganiła w zmiennych reszty skryptu. Komunikat błędu idzie przez >&2, czyli drugim głośnikiem, tym od błędów. cp -p zachowuje datę modyfikacji i uprawnienia oryginału, a -- mówi „koniec opcji”, na wypadek gdyby ktoś nazwał plik -rf. Tak, ludzie tak robią. Zwykle raz.
I zauważ ostatni przypadek: wywołanie bez argumentu. Funkcja mówi „nie ma pliku ''”. Działa, ale wygląda jak urzędnik, który na pusty formularz odpowiada „Pan Nikt nie figuruje w systemie”. Dziś nauczysz się to zrobić ładniej.
Co dalej
W zadaniu doklejasz datę do nazwy pliku. A co, jeśli chciałbyś z nazwy coś wyciąć, na przykład końcówkę .txt, albo zamienić spacje na podkreślniki, bez wołania żadnych zewnętrznych programów? Następna lekcja to nożyczki do tekstu wbudowane w samego Basha.