Programowanie w shellu (bash) napisał: ZyGfryD, 08:04 17-12-2002,
W tym artykule opiszę programowanie w shellu bash. Dlaczego? Jest kilka powodów: jest najbardziej popularny, w systemie pliki inicjujące są w nim napisane np. w katalogu /etc/rc.d/. Również spotkałem się z opinią: ?jeżeli już piszesz programy w powłoce, to pisz je w powłoce Bourne?a?. Pisanie w innych powłokach jest bardzo podobne. Wbrew pozorom programowanie w powłoce jest często przydatne i przyspiesza pracę w systemie, a zarazem jest całkiem proste. Ci, którzy używają na co dzień którejś z odmian Uniksa, na pewno potwierdzą to. Jak wygląda program napisany w shellu? Może to być zwykły ciąg instrukcji taki jak: if [ `ls -l /var/spool/mail/user | awk '{print $5}'` -gt 0 ]; then echo ?masz poczte?; fi jednak jest to niewygodne, więc wszelkie większe instrukcje przechowujemy w pliku. Taki plik może zawierać polecenia połączone w potoki, przeadresowania, zmienne, instrukcje sterujące a nawet proste funkcje. Zgodnie ze standardem POSIX taki plik rozpoczyna się od: #!/bin/bash ścieżki do interpretatora (w zależności od tego jaki chcemy używać) w pierwszej linii. Nazwa pliku nie jest ważna, natomiast koniecznym jest nadanie atrybutu +x czyli uruchamianie . [Polecenie: chmod +x nazwa_pliku ? red.] Taki plik od tej pory będzie nosił miano skryptu. Pierwszy program mógłby mieć postać: #!/bin/bash echo ?Witaj Linuksie? co spowoduje wypisanie tekstu ?Witaj Linuksie?. Chcąc umieścić komentarz w skrypcie używamy znaku #, który mówi o tym, że znaki umieszczone za nim nie będą interpretowane. Pamiętaj: #!/bin/bash to nie komentarz. Zmienne w skrypcie inicjujemy w prosty sposób pisząc: nazwa_zmiennej=wartość, a odwołujemy się do niej poprzez $nazwa_zmiennej, zmienną zerujemy poleceniem nazwa_zmiennej=. Wynikiem wykonania tego skryptu pokazanego w przykładzie 1 będzie: szczesliwy numer:5 5 szczesliwy numer: oraz dwie puste linie. Wywołanie polecenia echo -n powoduje brak przejścia kursora do następnej linii. Natomiast echo $liczba $tekst powoduje, że obie zmienne zostanę wydrukowane w tej samej linii. Zauważmy, że zmienne nie mają typu, jak to ma miejsce w językach wysokiego poziomu. Wczytywanie wartości do zmiennej z klawiatury odbywa się za pomocą polecenia read zmienna. #!/bin/bash echo -n ?podaj liczbe:? #!/bin/bash # to jest komentarz liczba=5 tekst=”szczesliwy numer:” echo -n $tekst echo $liczba echo $liczba $tekst liczba= tekst= echo $tekst echo $liczba Przykład 1: Najprostsze polecenia użyte w skrypcie. if [ $OSTYPE = ?Linux? ] then echo ?fajnie? else echo ?jakos to przezyje? fi Uwaga: za klamrą ?[? i przed ?]? powinien być odstęp. Testować możemy także wartości zmiennych: if [ $tekst1 = $tekst2 ] then echo ?takie same napisy? fi Sprawdzenie czy napisy są różne odbywa się za pomocą operatora !=. if [ $zmienna ] then echo ?zmienna nie jest pusta? fi if [ -f $zmienna ] then echo ?plik: ?$zmienna? istnieje? fi Inne warunki to: n ? napis ma długość większą od zera z ? napis ma zerową długość f ? plik istnieje d ? plik jest katalogiem r ? możesz czytać plik w ? możesz pisać do pliku x ? możesz uruchomić plik s ? plik ma długość większą od zera p ? plik jest łączem nazwanym Sprawdzanie wartości liczbowych ma postać: if [ $liczba -eq 5 ] then echo ?liczba jest równa 5? else echo ?liczba jest różna od 5? fi Możemy wykonywać testy takie jak: eq ? równe liczby ne ? nierówne liczby gt ? większa niż lt ? mniejsza niż ge ? większa bądź równa le ? mniejsza bądź równa Dostępne są również operatory logiczne ! ? negacja, -a ? iloczyn (and), -o ? alternatywa. Jeżeli mamy więcej warunków do sprawdzenia stosujemy instrukcję elif: if [ warunek ] then polecenie(a) #!/bin/bash echo "Napisz cos”; read l case $l in ”a” ) echo "Litera a”;; [A-Z] ) echo "Ktoras z duzych liter”;; ”Linuks” ) echo "Ulubiony system”;; [Kk][Oo][Nn][Ii][Ee][Cc] ) echo "Do widzenia”;; * ) echo "nie wiem co napisales” ;; esac Przykład 2: Wykorzystanie instrukcji case. elif [ warunek ] then polecenie(a) fi Kolejną przydatną instrukcją jest instrukcja case. Wywołanie wygląda w ten sposób: case $zmienna in wzorzec1 ) polecenie(a);; wzorzec2 ) polecenie(a);; . . wzorzecn ) polecenie(a);; * ) polecenie(a) domyślne;; esac *) oznacza, że żaden ze wzorców nie został wybrany i zostanie wykonane polecenie domyślne (przykład 2). Pętla while ma postać: while [ warunek ] do polecenie(a) done Jej działanie pokazuje przykład 3. Ta pętla będzie się wykonywała dopóki nie podasz napisu ?koniec?. Czyli dopóki warunek jest spełniony. Inną podobną w zapisie pętlą jest until. Zamiast while piszemy until. Pętla ta wykonuje się, gdy warunek nie jest spełniony. Zakończy się wtedy, gdy zostanie on spełniony. Porównajmy je (przykład 4). Pętla z przykładu 4(a) będzie cały czas wypisywać tekst (przerwać skrypt można wciskając ^C [czyli Ctrl-C ? red.]), ponieważ zmienna $l jest większa od jeden, czyli warunek jest spełniony. W przypadku pętli z przykładu4(b) nie zostanie wyświetlony napis, ponieważ warunek jest spełniony. #!/bin/bash while [ "$x” !="„koniec” ] do echo -n "napisz cos” read x echo "nie o to mi chodzilo” done Przykład 3: Pętla while. ) #!/bin/bash l=2 while [ $l -gt 1 ] do echo $l” jest wieksze od 1" done (b) #!/bin/bash l=2 until [ $l -gt 1 ] do echo $l” jest wieksze od 1 a mimo to nie zobaczysz tego napisu” done Przykład 4: Porównanie pętli until i while. Do dyspozycji mamy również instrukcę for. Kożystamy z niej w następujący sposób: for zmienna in lista do polecenie(a) done #!/bin/bash for slowo in Ala ma kota do echo $slowo done Przykład 5: Pętla for. Działanie tej instrukcji polega na tym, że ?zmienna? przybiera wartości po kolei podane w ?lista? jak w przykładzie 5. Ten skrypt wydrukuje kolejno słowa ?Ala ma kota? w nowych liniach. Jeżeli natomiast w tej instrukcji nie użyjemy listy i zapiszemy w ten sposób: for zmienna do polecenie(a) done wtedy ?zmienna? będzie przyjmowała za wartości parametry podane w wywołaniu skryptu. W pętlach while oraz for możesz używać instrukcji break i continue. Pierwsza z nich spowoduje wyjście z pętli do instrukcji następującej po niej, instrukcja continue powoduje przejście do początku pętli, do jej następnego przebiegu. #!/bin/bash function logo { echo "Logo programu” echo "uzytkownik (c)” } logo echo "tutaj inne instrukcje” logo Przykład 6: Funkcje w skrypcie. Proste menu możemy stworzyć instrukcją select. Ma ona podobne wywołanie jak for. select zmienna in lista do polecenie(a) done Menu to ma postać wyliczenia, pierwszy element ma numer 1. Chcąc wybrać określoną opcję wybieramy odpowiedni numer. Wtedy ?zmienna? ma wartość odpowiadającą n-temu elementowi z listy. Instrukcje możemy pogrupować w funkcjach, co często upraszcza skrypt oraz pozwala zmniejszyć jego długość. Funkcję tworzymy w ten sposób: function nazwa { polecenie(a) } a wywołujemy ją w skrypcie poprzez jej nazwę. W przykładzie 6 funkcja logo zostanie uruchomiona raz na początku programu i raz na końcu. Obliczanie wartości wyrażeń następuje za pomocą instrukcji expr, przy czym możemy używać operatorów arytmetycznych: +, -, *, /, logicznych & (and), | (or), ! (not). Przykłady: a=5 b=6 c=`expr $a * $b` echo $c wynikiem będzie oczywiście 30, (pamiętaj o odwrotnych apostrofach). Inkramentację możemy wykonać za pomocą expr: liczba=`expr $liczba + 1` lub prostszym zapisem: liczba=$[liczba+1]. Instrukcję exit używamy najczęściej podczas wyjścia z programu (wykonującego się skryptu). Gdy zapiszemy exit 0, wtedy programy, które ewentualnie będą korzystać z naszych skryptów, będą wiedziały, że program wykonał się poprawnie. Jeżeli chcemy powiadomić te programy o jakimś błędzie, zwracamy wartość różną od zera. Gdy chcemy zakończyć działanie programu, naciskamy kombinację klawiszy Ctrl+C lub Ctrl+\. W skryptach możemy zareagować na te sygnały za pomocą instrukcji trap polecenie sygnał(y). Gdy w skrypcie użyjemy polecenia: trap 'echo ?przerwanie programu?;echo ?koniec?;exit 1' 2 3 to podczas użycia klawiszy ^C lub ^\ zostaną wydrukowane napisy: ?przerwanie programu? i ?koniec? oraz zostanie zwrócony kod błędu. Chcąc nauczyć się więcej, możesz przeanalizować skrypty dostępne w systemie. Najważniejsze znajdziesz w katalogu /etc/rc.d/, są to skrypty inicjalizujące system, uruchamiają programy niezbędne do jego działania. Zresztą zobaczysz sam, co one robią. Kolejnymi katalogami, do których powinieneś zajrzeć są: /usr/sbin/, /usr/bin/, /var/log/scripts/, /var/log/setup/, /usr/X11R6/bin/ itd. Czy plik jest skryptem, który nas interesuje, możesz sprawdzić poleceniem file nazwa_pliku. Interesują nas pliki typu: Bourne-Again shell script text lub Bourne shell script text. Możemy je znaleźć pisząc skrypt pokazany w przykładzie 7. W moim systemie ten skrypt znalazł ponad 300 plików. Potężna lektura do nauki;) Szegółowy opis powłok znajdziesz w manualach (man bash, man tcsh), jak również uruchamiając info gdzie jest opisany między innymi zsh. Idealną książką do nauki pisania skryptów jest: ?Unix dla użytkowników DOS-u?, autor Kenneth Pugh, WNT. !/bin/bash pliki=`find / -mount` #szuka wszystkie pliki poza katalogami zamontowanymi i zapisuje je do $pliki for plik in $pliki #wykonuje polecenia dla każdego pliku na li¶cie $pliki do if [ -f $plik ] #czy to jest plik then #jeżeli tak to { rodzaj=`file $plik | grep Bourne` #czy to jest skrypt sh lub bash (czy łańcuch zawiera słowo Bourne) if [ ”$rodzaj” != ”” ] #jeżeli tak (łańcuch nie jest pusty) then #to echo ”znalazlem skrypt: ”$plik #wypisz jego nazwę echo $plik >> lista #zapisz j± do pliku „lista” i=$[i+1] #policz te pliki fi } fi done echo ”Znalazlem ”$i” skryptow” #napisz ile ich znalazłes Przykład 7: Skrypt wyszukujący skrypt Użyteczne polecenia zewnętrzne Programy przydatne przy pisaniu skryptów: • awk – potężne narzędzie do manipulowania wzorcami — koniecznie się z nim zapoznaj • basename ścieżka — zwraca nazwę pliku z podanej ścieżki • cat — wczytywanie plików i wyświetlanie na standardowe wyjście • clear — czyszczenie ekranu • cmp — porównywanie plików • cut — wycinanie kolumn lub pól • date — wyświetla datę i czas • dirname ścieżka — zwraca nazwę katalogu ze ścieżki • find ścieżka parametry — szukanie plików • grep wzorzec plik(i) — wyszukuje wzorzec w pliku. • hostname — sama nazwa mówi za siebie • kill sygnał proces — wysłanie sygnału do określonego procesu (kill -l — lista sygnałów) • last — ostatnio zalogowani • more — często używany w potoku do zatrzymania wyświetlanego wyjścia (np: cat /etc/passwd | more) • ps — wyświetla aktywne procesy • pwd — wyświetla ścieżkę bieżącego katalogu • sed — edytor strumieniowy, zamiana lub modyfikacja wierszy • sleep czas — zatrzymanie, przez „czas”— określony w sekundach • sort plik — sortowanie pliku • strings — wyszukiwanie ciągów znaków np. w plikach binarnych • w — wyświetla użytkowników zalogowanych • wc — liczy znaki: -c, linie -l, słowa -w To tylko część z nich, o każdym możesz dowiedzieć się więcej używając polecenia man program lub program --help.
damdamok