===== Funkcje w powłoce ===== ==== POSIX shell ==== Aby zdefiniować [[https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_09_05|funkcję]] w powłoce, należy użyć składni ''**//nazwa//() //treść//**'', gdzie //treść// powinna być [[https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_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 ~ $++++
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/9799919799/utilities/V3_chap02.html#tag_19_29|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.
Zwróć uwagę na to, jakie znaczenie ma użycie pojedynczych bądź podwójnych cudzysłowów przy podawaniu polecenia:
user@host ~ $ cat trap.sh #!/bin/sh X=bogus trap 'printf "single quotes: "; echo $X' USR1 trap "printf 'double quotes: '; echo $X" USR2 for X in 1 2 3; do sleep 1 done user@host ~ $ ./trap.sh & sleep 2; kill -USR1 $!; kill -USR2 $!; wait [1] 25178 single quotes: 2 double quotes: bogus [1]+ Zakończono ./trap.sh user@host ~ $Dla czytelności i wygody często pisze się funkcję dla obsługi zdarzenia i podaje jej wywołanie w komendzie ''trap''. ~~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/9799919799/utilities/V3_chap02.html#tag_19_19|.]] //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/9799919799/utilities/V3_chap02.html#tag_19_20|eval]]'' wykonuje podaną jako tekst komendę. Komenda ''[[https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_21|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.
user@host ~ $ X=2 Y=X Z=\$$Y user@host ~ $ echo $Z $X user@host ~ $ eval "echo $Z" 2 user@host ~ $ echo $$ 27197 user@host ~ $ sh sh-5.2$ echo $$ 27224 sh-5.2$ exit exit user@host ~ $ echo $$ 27197 user@host ~ $ exec sh sh-5.2$ echo $$ 27197~~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/9799919799/utilities/basename.html|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/9799919799/utilities/dirname.html|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/9799919799/utilities/fc.html|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 Zamknięcie w podwójny cudzysłów wyrażeń ''"${TABLICA[@]}"'' i ''"${!TABLICA[@]}"'' działa tak jakby została w to miejsce podstawiona każda wartość osobno zamknięta w podwójny cudzysłó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/9799919799/utilities/xargs.html|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 5Moż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''((Przełącznik ''-0'' dla programu ''xargs'' stał się częścią standardu POSIX od 2024 roku.)) 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 ~~