===== Funkcje w powłoce ===== ==== POSIX shell ==== Aby zdefiniować [[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_05|funkcję]] w powłoce, należy użyć składni ''**//nazwa//() //treść//**'', gdzie //treść// powinna być [[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_04|złożoną komendą]] (np. poleceniami zamkniętymi w ''{ … }''). \\ Wywołanie takiej funkcji realizuje się przez ''//nazwa// [//argument//]...'', przy czym wewnątrz funkcji zmienne ''$#'' oraz ''$1'', ''$2'', ... dotyczą właśnie //argument//ów funkcji. Wewnątrz funkcji można też używać polecenia ''return [//n//]'' które kończy funkcję zwracając wartość //n// lub, jeśli nie podano //n//, wartość ''$?''. Dla funkcji //nazwa// polecenie ''type //nazwa//'' zwróci przynajmniej informację że //nazwa// jest funkcją, a w Bashu dodatkowo wyświetli jej ciało (podobnie jak komenda ''declare -f //nazwa//'').

Przykład:

user@host ~ $ colorEcho() {
> case "$1" in
>   red) CCODE=31;;
>   green) CCODE=32;;
>   blue) CCODE=34;;
>   *) CCODE=5;;
> esac
> echo -ne "\033[${CCODE}m";
> echo -n "$2";
> echo -e "\033[0m";
> }
user@host ~ $ colorEcho red riding hood
riding
user@host ~ $ colorEcho green "anne of green gables"
anne of green gables
++++ Wynik type colorEcho |
user@host ~ $ type colorEcho
colorEcho jest funkcją
colorEcho ()
{
    case "$1" in
        red)
            CCODE=31
        ;;
        green)
            CCODE=32
        ;;
        blue)
            CCODE=34
        ;;
        *)
            CCODE=5
        ;;
    esac;
    echo -ne "\033[${CCODE}m";
    echo -n "$2";
    echo -e "\033[0m"
}
user@host ~ $
++++
Inny przykład: compressInDateDir() { [ $# -ge 2 ] || return 1 TARNAME="$(readlink -f "$1")" shift WORKDIR="$(mktemp -dp. || mktemp -d)" [ "$WORKDIR" ] || return 1 DATE=$(date +%Y_%m_%d) mkdir "$WORKDIR/$DATE" cp -al "$@" "$WORKDIR/$DATE" tar cJf "$TARNAME" -C "$WORKDIR" "$DATE" rm -rf "$WORKDIR" } ==== Bash ==== Powłoka Bash wprowadza parę rozszerzeń do funkcji. \\ Po pierwsze, w Bashu można poprzedzić funkcję słowem kluczowym ''function''.\\ Po drugie, **w Bashu można deklarować lokalne zmienne wewnątrz funkcji poprzedzając ich nazwę słowem kluczowym ''local''**.
user@host ~ $ X=5;
user@host ~ $ up() { X=$((X+$1)); echo $X; }
user@host ~ $ up 1
6
user@host ~ $ up 1
7
user@host ~ $ up() { local X=$((X+$1)); echo $X; }
user@host ~ $ up 1
8
user@host ~ $ up 1
8
user@host ~ $ echo $X
7
==== Ćwiczenia ==== ~~Zadanie.#~~ Napisz funkcję która wyświetla ''hello world''. ~~Zadanie.#~~ Napisz funkcję przyjmującą dwa argumenty: liczbę (ilość powtórzeń) i tekst. Funkcja ma wypisać na standardowe wyjście podany tekst podaną ilość razy. ~~Zadanie.#~~ Napisz funkcję przyjmującą jako argument nazwę pliku. Jeśli taki plik nie istnieje, w wyniku działania funkcji taki plik ma zostać utworzony bez praw dostępu dla grupy i pozostałych. \\ Możesz użyć polecenia ''umask'' (lepsze rozwiązanie) lub ''chmod'' (gorsze rozwiązanie). ~~Zadanie.#~~ Napisz funkcję zastępującą program ''tree'': funkcja ma przeszukać w głąb (DFS) pliki zaczynając od podanego katalogu i wypisać dla każdego pliku jego nazwę z odpowiednimi wcięciami. ~~Zadanie.#~~ Napisz funkcję liczącą rekurencyjne silnię i wypisującą jej wynik na standardowe wyjście. ===== Obsługa sygnałów ===== Komendą ''[[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_28|trap]] //polecenie// //SYGNAŁ//'' można ustawić obsługę podanego //SYGNAŁ//u oraz, podając zamiast nazwy sygnału słowo EXIT, ustawić polecenie do wykonania bezpośrednio przed zakończeniem procesu powłoki. \\ **W powłoce obsługa sygnału ustawiona komendą ''trap'' jest wykonywana bezpośrednio po zakończeniu polecenia w trakcie którego odebrano sygnał.** \\ Polecenie musi być podane jako ciąg tekstu. \\ Polecenie ustawione jako EXIT nie jest wykonywane tylko jeśli powłoka kończy pracę w wyniku sygnału KILL lub polecenia ''exec''. \\ Składnia ''trap - //SYGNAŁ//'' przywraca domyślną obsługę sygnału. ~~Zadanie.#~~ Dodaj obsługę sygnału INT (generowanego przez ''Ctrl+c'') do skryptu wykonującego ''sleep //czas//'' oraz ''echo //tekst//''. Porównaj przerwanie komendy ''sleep'' sygnałem INT z przerwaniem skryptu sygnałem INT. ~~Zadanie.#~~ Stwórz w skrypcie plik tymczasowy komendą ''mktemp''. Ustaw obsługę zdarzenia wyjścia z powłoki tak, by usuwała ten plik i wyświetlała tekst. Umieść w skrypcie komendę ''sleep //czas//'' oraz ''echo //tekst//''. \\       • Porównaj przerwanie komendy ''sleep'' sygnałem INT z przerwaniem skryptu sygnałem INT \\       • Sprawdź czy przerwanie skryptu sygnałem TERM usuwa plik. \\       • Sprawdź czy przerwanie skryptu sygnałem KILL usuwa plik. ===== To co nie trafiło gdzie indziej ===== === source === Komenda ''[[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_18|.]] //plik//'' (i komenda ''source //plik//'' w Bashu) uruchamiają polecenia z podanego pliku w bieżącej powłoce (tzn. tak jakby były wpisane z klawiatury). ~~Zadanie.#~~ Wpisz do pliku //plik// ustawienie wartości kilku zmiennym. Wczytaj ten plik komendą ''.'' (lub ''source'') i sprawdź wartości tych zmiennych. ~~Zadanie.#~~ Wpisz do pliku //plik// polecenie ''exit''. Nadaj mu prawa do wykonywania. Wykonaj ten plik (tzn. wykonaj ''./plik'') oraz użyj komendy ''.'' na tym pliku (tzn. wykonaj ''. ./plik''). === eval i exec === Komenda ''[[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_19|eval]]'' wykonuje podaną jako tekst komendę. Komenda ''[[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_20|exec]]'' zawierająca tylko przekierowania ustawia podane przekierowania dla powłoki (było przy okazji przekierowań), natomiast z podaną komendą zastępuje powłokę podanym poleceniem. ~~Zadanie.#~~ Umieść w skrypcie ''eval xterm'' oraz ''echo eval'' i uruchom ten skrypt. \\ Następnie umieść w skrypcie ''exec xterm'' oraz ''echo exec'' i uruchom ten skrypt. === yes === Komenda ''yes [//tekst//]'' wypisuje w kółko //tekst// na standardowe wyjście. Domyślnie wypisuje tekst ''y''. \\ ''yes'' jest używane do generowania odpowiedzi dla programów które pytają ''czy na pewno chcesz...'' i nie mają przełącznika który wyłącza takie pytania. ~~Zadanie.#~~ Stwórz pliki ''//plik_1//'', ''//plik_2//'', … , ''//plik_9//''. Spróbuj je usunąć komendą ''rm -i plik_?''. Połącz tą komendę z komendą ''yes'' tak żeby ''yes'' potwierdził usunięcie plików. === basename i dirname === Program ''[[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html#tag_20_07|basename]] //ścieżka// [//rozszerzenie//]'' wypisuje na standardowe wyjście nazwę pliku wskazanego ścieżką, opcjonalnie usuwając z nazwy //rozszerzenie//. Program ''[[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html#tag_20_35|dirname]] //ścieżka//'' wypisuje na standardowe wyjście katalog w którym znajduje się plik wskazany ścieżką. ''$(basename "$//X//")'' i ''$(dirname "$//X//")'' w wielu przypadkach można zastąpić przez ''${//X//##*/}'' i ''${//X//%/*}''. Należy pamiętać, że ''basename'' i ''dirname'', w odróżnieniu od podstawień, poradzą sobie ze zniekształconymi ścieżkami. ~~Zadanie.#~~ Przetestuj ''basename'' i ''dirname'' dla: \\       • ''../../../etc/passwd'' \\       • ''/usr/share/icons/hicolor/'' \\       • ''plik'' === fc === Komenda ''[[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/fc.html#tag_20_44|fc]]'' tworzy plik tymczasowy, wstawia do niego poprzednią komendę z historii, otwiera ten plik w edytorze tekstu, a po zamknięciu edytora wykonuje poprawioną komendę. ~~Zadanie.#~~ Wykonaj jakąś długą komendę (np. ''for X in $(grep -lR 'terminal' /usr/share/applications/); do grep "Name" $X; done''), następnie wpisz ''fc'', zmodyfikuj ją i wykonaj. ===== Tablice w Bashu [extra] ===== Poza zwykłymi zmiennymi, powłoka Bash wspiera zwykłe tablice (indeksowane kolejnymi liczbami naturalnymi) oraz tablice asocjacyjne (indeksowane tekstowymi kluczami). Skrócony opis poniżej; pełen w [[https://www.gnu.org/software/bash/manual/html_node/Arrays.html|podręczniku Basha]]. ==== Zwykłe ==== Zwykłą tablicę o nazwie ''//TABLICA//'' tworzy się przez: * komendę: ''declare -a TABLICA'' * przypisanie pustej tablicy: ''TABLICA=()'' * przypisanie tablicy z zawartością: ''TABLICA=(//wartość0// [//wartość1//]...)'' * ustawienie wartości elementu tablicy: ''TABLICA[//0//]=//wartość//'' Dodanie nowych elementów pod wskazany indeks realizuje się komendą ''TABLICA[//indeks//]=//wartość//'', natomiast dodanie na koniec komendą ''TABLICA+=(//wartość1// [//wartość2//]...)''. Odwołanie się do zmiennej //TABLICA// spowoduje: * ''$TABLICA'' podstawi wartość elementu pod indeksem 0 * ''${TABLICA[//indeks//]}'' podstawi wartość elementu pod indeksem //indeks// * ''${TABLICA[@]}'' podstawi wszystkie wartości w tablicy * ''${!TABLICA[@]}'' podstawi wszystkie indeksy w tablicy * ''${#TABLICA[@]}'' podstawi ilość elementów ==== Asocjacyjne ==== Asocjacyjną tablicę o nazwie ''//TABLICA//'' tworzy się przez komendę ''declare -A TABLICA''. Dodanie nowych elementów można wykonywać przez: * ''TABLICA[//klucz//]=//wartość//'' * ''TABLICA+=([klucz1]=wartość1 [klucz2]=wartość2 ...)''       (uwaga: tutaj ''['' są elementem składni) * ''TABLICA+=(klucz1 wartość1 klucz2 wartość2 ...)'' Przy czym dwie ostatnie składnie, przy zastąpieniu ''+='' przez ''='', służą do nadpisania tablicy podaną listą kluczy i wartości. Odwoływanie się do tablicy asocjacyjnej jest identyczne jak dla zwykłej. ===== xargs i parallel ===== ==== xargs ==== Program ''**[[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html#tag_20_158|xargs]]** //program//'' czyta ze standardowego wejścia kolejne nazwy i wykonuje ''//program//'' z tymi nazwami jako argumentem. \\ ''xargs'' jest głównie wykorzystywany w potokach, do uruchomienia podanego programu z listą argumentów generowanych przez potok. Poglądowo, np. ''potok | xargs program'' wykonuje mniej więcej ''program $(potok)'', natomiast np. ''potok | xargs -n1 program'' wykonuje mniej więcej ''for ARG in $(potok); do program $ARG; done'', ale potok i program są wywoływane jednocześnie. Domyślnie ''xargs //program//'' wykonuje //program// wpisując na koniec listy argumentów wszystkie przeczytane nazwy naraz. \\ Można określić po ile argumentów naraz ma być wstawianych na koniec podanej komendy przełącznikiem ''-n'':
user@host ~ $ seq 5 | xargs echo -e
1 2 3 4 5
user@host ~ $ seq 5 | xargs -n 2 echo -e
1 2
3 4
5
Można też użyć przełącznika ''-I //słowo//'' do wskazania w które miejsce linii poleceń ma być wstawiony każdy argument (co implikuje uruchomienie komendy osobno dla każdego argumentu):
user@host ~ $ seq 3 | xargs -I ARG echo ">>ARG<< >>ARG<<"
>>1<< >>1<<
>>2<< >>2<<
>>3<< >>3<<
Polecenie ''xargs'' rozdziela standardowe wejście po dowolnych białych znakach, co powoduje problemy np. ze spacjami. \\ Większość implementacji polecenia ''xargs'' rozumie przełącznik ''-0'' rozdzielający nazwy znakiem ''\0'':
user@host ~ $ fortune
BOFH Excuse #443:
Zombie processes detected,
machine is haunted.
user@host ~ $ fortune | xargs -n1 echo " *"
 * BOFH
 * Excuse
 * #443:
 * Zombie
 * processes
 * detected,
 * machine
 * is
 * haunted.
user@host ~ $ fortune | tr '\n' '\0' | xargs -n1 -0 echo " *"
 * BOFH Excuse #443:
 * Zombie processes detected,
 * machine is haunted.
Przykładowe przełączniki dla różnych programów generujące znak ''\0'' do oddzielenia kolejnych elementów opisuje nawet [[https://en.wikipedia.org/wiki/Xargs#Separator_problem|Wikipedia dla hasła xargs]]. [[https://www.gnu.org/software/findutils/manual/html_node/find_html/xargs-options.html|GNU xargs]] (zwykle domyślny w Linuksie) rozumie też przełącznik ''-P [//num//]'' który pozwala uruchomić //num// równoległych procesów (jeśli podano ''-P'' bez argumentów, odpalane jest tyle procesów z ilu rdzeni można skorzystać). ~~Zadanie.#~~ Stwórz katalogi o nazwach ''001'', ''002'', ''003'', ..., ''999'' używając ''xargs''. ~~Zadanie.#~~ Policz sumy kontrolne MD5 dla plików ''*.pdf'' znalezionych narzędziem ''locate'' w katalogu ''/usr/share/''. \\ Wykonaj to zadanie zarówno używając ''xargs'' jak i używając pętli w powłoce. ~~Zadanie.#~~ Skopiuj pliki z katalogu ''/usr/include'' zawierające tekst ''typedef signed char'' do katalogu ''/tmp/'' używając xargs. \\ Komenda ''grep'' z przełącznikiem ''-l'' wypisuje tylko nazwy pasujących plików, a z przełącznikiem ''-R'' szuka rekurencyjnie. ==== GNU parallel [ekstra] ==== Do uruchamiania współbieżnie m. inn. programu dla argumentów czytanych ze standardowego wejścia można też wykorzystać program [[https://en.wikipedia.org/wiki/GNU_parallel|parallel]]. Dla tego zastosowania jest praktycznie zamiennikiem ''xargs''. \\ Z odpowiednimi argumentami ''parallel'' pozwala też uruchamiać dowolne zadania współbieżnie kontrolując ilość jednocześnie uruchomionych zadań. \\ Więcej informacji na [[https://www.gnu.org/software/parallel/|gnu.org/software/parallel]]. ~~META: language = pl ~~