Bash dla Hobbystów. Funkcje

Ostatni tekst z cyklu wpisów poświęconych podstawom skryptowania Bash. Tematem zamykającym są funkcje.

Wstęp

W przypadku skryptowania w Bashu temat funkcji jest nierzadko pomijany. Na ten przykład w książce Briana Warda “Jak działa Linux” w części poświęconej skryptowaniu nie ma o tym ani słowa, tak samo z resztą w przypadku innej przeze mnie polecanej pozycji, czyli “Linux. Wprowadzenie do wiersza poleceń.” Williama Shottsa. Oczywiście obie pozycje nie są o skryptowaniu sensu stricto, więc pewnie da się jakoś to zrozumieć, natomiast ja osobiście wahałem się na początku, czy w ogóle o tym pisać. Muszę bowiem przyznać w tym miejscu, że z mojej perspektywy nic wielkiego by się nie stałoby, gdyby funkcje w Bash w ogóle nie występowały. Oczywiście – podobnie jak w innych językach programowania – jest to znakomity sposób na poprawienie czytelności kodu oraz unikanie powtarzania całych bloków kodu, co później tylko utrudnia jego utrzymanie. Tym niemniej staram się trzymać zasady, że skrypty w Bashu nie powinny być przesadnie długie, zaś do bardziej skomplikowanych zadań lepiej wybrać innych język wysokiego poziomu jak chociażby Python. Tym niemniej skoro są one w Bash dostępne to należałoby przynajmniej o nich wspomnieć.

Funkcje

Tworzenie funkcji w Bashu jest dość banalne, choć można spotkać ich deklaracje w dwóch równoważnych postaciach:

nazwa_funkcji () {

//lista instrukcji

}

lub

function nazwa_funkcji {

//lista instrukcji

}

Osobiście preferuję podejście pierwsze ze względu na występowanie tam nawiasów, choć w tym wypadku jest to jedynie kwestia przyzwyczajenia do pewnej konwencji, ponieważ akurat tutaj te nawiasy robią wyłącznie za swoistą dekorację, o czym za moment się przekonamy analizując poniższy przykład:

#!/bin/bash


print_hello () {
   echo "Hello World!"
}


function print_helloPerson {
   echo "Hello $1"
}


print_hello
print_helloPerson Rafał

Na początku zadeklarowane zostały dwie funkcje (print_hello oraz print_helloPerson), każdą przy pomocy innej konwencji. Następnie te funkcje zostały wywołane, ale jak widać w obu wypadkach bez użycia nawiasów, które zwyczajowo pojawiają się w przypadku innych języków programowania.

Przekazywanie parametrów do funkcji

Co więcej do drugiej funkcji udało się nam nawet przekazać parametr, który następnie został obsłużony w ciele funkcji. Do tego celu został wykorzystany mechanizm parametrów pozycyjnych, ten sam który omawiany był przy okazji drugiego tekstu o zmiennych (pierwsza wartość podana po funkcji odpowiada zmiennej $1, druga $2 i tak dalej). Innymi słowy nic nowego za jednym wszakże wyjątkiem: jeśli spodziewasz się, że przy pomocy zmiennej $0 będziesz w stanie wyświetlić nazwę funkcji, to się srogo zawiedziesz.

#!/bin/bash


!/bin/sh
funkcja()
{
 echo "Do funkcji przekazane zostały następujące argumenty: $*"
 echo "Liczba argumentów funkcji: $#"
 echo "Nazwa funkcji: $0"
 return 0
}


funkcja a b c


exit 0

W wyniku działania tego skryptu w terminalu dostaliśmy najpierw informację o wszystkich przekazanych do funkcji argumentach, następnie ich liczbę. Tym niemniej na końcu zamiast oczekiwanej nazwy funkcji pojawiła się nazwa naszego skryptu.

Wartości zwracane

Skoro – jak widzieliśmy wcześniej – funkcja podczas wywoływania może przyjmować różne wartości jako kolejne argumenty, to zapewne spodziewalibyśmy, że w wyniku jej działania istnieje również możliwość przekazania zwrotnie jako wyniku pewnej wartość. Oczywiście jest to słuszne założenie, choć niestety – nie pierwszy raz tak się dzieje w przypadku Basha – w dość nieoczywisty sposób jest zaprojektowane. Otóż w ciele funkcji możemy użyć słowa kluczowego return, ale działa ono inaczej niż przyzwyczaiły nas do tego inne języki programowania. W Bash polecenie to spełnia dla funkcji analogiczną rolę jak słowo exit dla całego skryptu, czyli przy jego pomocy możemy zwrócić kod wyjścia dla zakończonego właśnie wywołania funkcji a nie faktyczny efekt działania funkcji. Tym samym wartość w ten sposób zwracana powie nam jedynie czy wywołanie funkcji zakończyło się powodzeniem lub jakimś błędem.

Spójrzmy na kod niżej:

#!/bin/bash


test(){  
  return 5
}


pierwsza_zmienna=$(test)
echo "Pierwsza zmienna: $pierwsza_zmienna"


test
druga_zmienna=$?
echo "Druga zmienna: $druga_zmienna"

Do pierwszej zmiennej przypisaliśmy bezpośrednio wynik działania funkcji test, zaś w drugim przypadku najpierw wywołaliśmy samą funkcję i dopiero później do kolejnej zmiennej przypisaliśmy wartość jej wywołania za pomocą notacji $?. Dopiero w tym drugim przypadku otrzymaliśmy pożądany komunikat w terminalu.

Oczywiście można próbować w ten sposób zwracać wynik działania funkcji, ale po pierwsze to mało eleganckie rozwiązanie – jesteśmy raczej przyzwyczajeni do zapisu, w którym wynik działania funkcji przypisujemy bezpośrednie do zmiennej bez konieczności rozkładania tego na dwa kroki. Po drugie – co znacznie ważniejsze – w ten sposób możemy obsłużyć dość wąską listę wartości, ponieważ słowo kluczowe return przyjmuje wyłącznie liczby z zakresu od 0 do 255.

Problem ten można rozwiązać chociażby (napisałem “chociażby” ponieważ to nie jedyny sposób, choć moim zdaniem najprostszy) poprzez użycie polecenia echo, tak jak zostało to zrobione w przykładzie poniżej. Oczywiście trzeba przy tym uważać, ponieważ jeśli polecenie to zostanie więcej niż raz, to w wyniku działania funkcji otrzymamy zwrotnie kilka wartości, co może nie być po naszej myśli.

#!/bin/bash


test1(){
echo 5
}
test2(){
   echo 5
   echo "kot"
}


zmienna1=$(test1)
echo "Wartość pierwszej zmiennej to: $zmienna1"


zmienna2=$(test2)
echo "Wartość drugiej zmiennej to: $zmienna2"


exit 0

Na zakończenie

W zasadzie dzisiejszy tekst zamyka serię wpisów o skryptowaniu w powłoce Bash. Mimo że temat nie należał do najbardziej złożonych, to stanowczo zbyt długo czasu zajęło mi jego zakończenie. Złożyły się na to bardzo różne kwestie, ale chyba w największym stopniu fakt, że pisanie o czymś, co jest mi lepiej lub gorzej znane, nie sprawia mi takiej frajdy jak pisanie o rzeczach, które dopiero odkrywam. Tym niemniej z całą pewnością nie jest to ostatni wpis w tym temacie, choć już może o nieco bardziej praktycznym charakterze. 

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *