Organizacja kodu w skryptach Bash

W każdym języku dłuższe programy można organizować korzystajac z funkcji i plików z funkcjami nie inaczej jest w Bashu, co prawda nie jest językiem wspierajacym pisanie obiektowe - ale kto tego potrzebuje pisząc w gruncie rzeczy proste skrypty powłoki?

Funkcje

To coś w rodzaju podprogramów. Stosuje się je gdy w naszym skrypcie powtarza się jakaś grupa poleceń (po co pisać je kilka razy), skoro można to wszystko umieścić w funkcji.

Do danej funkcji odwołujemy się podając jej nazwę, a wykonane zostanie wszystko co wpisaliśmy między nawiasy { }, skraca to długość skryptu, a więc ilość miejsc w których moglismy się pomylić, oprócz tego ułatwia zrozumienie kodu (nie trzeba czytać wielokrotnietego samego).

Składnia

W Bashu są dwa równoważne sposoby definiowania funkcji

Pierwszy sposób Drugi sposób
function nazwa_funkcji {
    polecenie1
    polecenie2
    polecenie3
}

Podpowiedź

Pierwszy sposób deklaracji funkcji jest znacznie czytelniejszy, ponieważ nazwę funkcji umieszczamy po słowie kluczowym function.

nazwa_funkcji() {
    polecenie_1
    polecenie_2
    polecenie_3
}

Ostrzeżenie

O mniejszej czytelności drugiego sposobu może świadczyć fakt wykorzystania go podczas realizacji ataku bash fork-bomb.

Przykład

#!/bin/bash

function napis {
    echo "To jest napis"
}

napis

W powyższym przykładzie mamy zdefiniowaną funkcję o nazwie napis. Odwołujemy się do niej podając jej nazwę - wykonane zostaną wtedy wszystkie polecenia, jakie jej przypiszemy (znajdujące się w ciele funkcji).

johny@ThinkPad:~$ function napis {
>     echo "To jest napis"
> }
johny@ThinkPad:~$
johny@ThinkPad:~$ napis
To jest napis

Ostrzeżenie

Niestety częściej stosowany do deklaracji funkcji jest sposób bez słowa kluczowego function - wynika to z tego, że jest w nim mniej pisania i jest on bardzo podobny do tego znanego z języka ANSI C.

Funkcje - przekazywanie parametrów

Przekazanie parametrów do funkcji następuje dokładnie tak samo jak do każdego polecenia które jest w naszym skrypcie, realizuje się to podając po nazwie funkcji dodatkowe argumenty.

nazwa_funkcji parametr_1 parametr_2

Przykład

#!/bin/bash

funkcja_z_parametrami() {
    echo "Przekazano $# parametrów"
    echo "Parametr $1"
    echo "Parametr $2"
}

funkcja_z_parametrami "param1" "param2"
johny@ThinkPad:~$ funkcja_z_parametrami "param1" "param2"
Przekazano 2 parametrów
Parametr param1
Parametr param2

Ostrzeżenie

Zmienna specjalna $0 przechowujaca nazwę skryptu nie jest dostępna! Choć na pierwszy rzut oka wydaje się, że powinna przechowywać nazwę funkcji.

Pliki dołączane zawierające definicje funkcji

Funkcje moga się znajdować w innym pliku, co uczyni nasz skrypt bardziej przejrzystym i wygodnym.

Definiowanie pliku zawierającego funkcje nie jest trudne wystarczy utworzyć skrypt (dokładnie taki sam jak każdy do tej twory tworzony) i dołączyć go do naszego głownego „programu”.

Podpowiedź

Dołączanie pliku można zrealizować na dwa sposoby - poprzez skorzystanie z polecenia source lub polecenia . (kropka).

Efekty uzyskane za ich pomocą sa identyczne.

Do pliku o nazwie: naszplik_z_funkcjami.sh wpisujemy dwie funkcje:

function nazwa_funkcji {
    polecenie_1
}

nazwa_funkcji2() {
    polecenie_1
}

następnie wczytujemy ten plik jednym z dwóch sposobów

Pierwszy sposób Drugi sposób
source ~/naszplik_z_funkcjami.sh

Podpowiedź

Wczytanie pliku z funkcjami przy wykorzystaniu polecenia source jest czytelniejsze.

. ~/naszplik_z_funkcjami.sh

i możemy wywołać polecenia: nazwa_funkcji i nazwa_funkcji2

Podpowiedź

Funkcje z pliku możemy wczytać do:

  • bieżącej interaktywnej powłoki,
  • do skryptu uruchomionego z linii poleceń

Przykład

Utwórzmy plik plik_z_funkcja.sh którego zawartość jest taka jak poniżej:

#!/bin/bash
function nasza_funkcja {
    echo -e 'Właśnie użyłeś funkcji o nazwie "nasza_funkcja".\a'
}

następnie wczytajmy plik plik_z_funkcja.sh do aktualnej powłoki i wywołamy funkcje nasza_funkcja

johny@ThinkPad:~$ echo "Echo test"
Echo test
johny@ThinkPad:~$ source plik_z_funkcja.sh
johny@ThinkPad:~$ nasza_funkcja
Właśnie użyłeś funkcji o nazwie "nasza_funkcja".

Łączenie instrukcji w bloki

Wszędzie tam, gdzie musimy użyć pojedynczej instrukcji, możemy zastosować blok instrukcji, czyli ciąg instrukcji ujęty w nawiasy klamrowe lub zwykłe.

Składnia łączenia poleceń wewnątrz biezącej powłoki

{
    ciąg poleceń
    ...
}

W przypadku nawiasów klamrowych polecenia są wykonywane w bieżącej powłoce i środowisku. Wartością takiego bloku jest wartość ostatniej wykonanej instrukcji.

Przykład:

#!/bin/bash
if true && { false;false;true } ; then
    echo TRUE
fi

Powyższy skrypt wypisze TRUE, ponieważ ostatnia instrukcja zwraca wartość 0 (true).

Składnia łączenia poleceń przy uruchomieniu powłoki potomnej

(
    ciąg poleceń
    ...
)

Dla poleceń w nawiasach zwykłych tworzona jest nowa powłoka. Wartością zwracaną jest kod ostatniej instrukcji bądź wartość zwrócona przez komendę exit (notabene to jest zwrócenie wartości ustalonej wcześniej).

Przykład

if true && (false;false;exit 0) ; then
    echo TRUE
fi

Powyższy skrypt wypisze TRUE, ponieważ ostatnia instrukcja zwraca wartość 0 (true).