JustPaste.it

Podstawy basha

Wprowadzenie


Pierwszy skrypt

Naukę programowania w shellu zacznijmy od zwyczajowego Hello, World!. Program w shellu, nazywany najczęściej skryptem, piszemy w edytorze tekstowym. Można używać dowolnego edytora, przy czym doświadczeni użytkownicy systemów unikso(wych|podobnych) będą używali Vima lub Emacsa, a nowicjusze prostych edytorów takich jak gedit. Nawet jeżeli używasz edytora pracującego w trybie graficznym jak gedit, uruchamiaj go z terminala, to dobry nawyk przy pracy w shellu. Otwórz edytor, np.:

$ gedit hello.sh

I napisz:

#!/bin/sh
echo Hello, world!

Następnie zapisz plik na dysku i wróć do terminala. Skrypt, żeby mógł być uruchomiony, musi mieć nadany atrybut wykonywalności. Nadaj atrybut wykonywalności:

$ chmod u+w hello.sh

A następnie uruchom plik:

$ ./hello.sh

Przeanalizujmy ten skrypt. Pierwsza linijka zaczynająca się od znaków #! określa interpreter, czyli program który będzie czytał dany skrypt i interpretował go. Druga linijka to polecenie echo, którego zadaniem jest wypisanie na podanych argumentów na standardowe wyjście. W tym przypadku są to dwa argumenty, "Hello," oraz "world!".


Liczby od 1 do 10

Uruchom taki skrypt:

#!/bin/sh
for i in `seq 10`
do
echo "i = $i"
done

Skrypt wypisuje na ekran liczby od 1 do 10. W kodzie skryptu nie widać nigdzie wpisanych wszystkich 10 liczb, więc muszą być w jakiś sposób generowane. Zajmuje się tym polecenie seq, którego zadaniem jest wypisywanie na standardowe wyjście jakiegoś zakresu liczb. Zobaczmy jak to działa:

maciej@matilda ~ $ seq 3
1
2
3

W naszym skrypcie polecenie seq 10 jest ujęte w `lewe apostrofy`. Oznacza to że to co jest wewnątrz apostrofów ma zostać uruchomione, a to co wewnętrzne polecenie wyśle na standardowe wyjśice, ma zostać wstawione w miejsce wyrażenia w lewych apostrofach. W tym przypadku po wykonaniu seq i wstawieniu nasz skrypt będzie wyglądał tak:

#!/bin/sh
for i in 1 2 3 4 5 6 7 8 9 10
do
echo "i = $i"
done

Argumenty poleceń

Każde polecenie w shellu składa się z nazwy uruchamianego programu (lub polecenia wbudowanego w shell) i parametrów. Parametry są oddzielone od siebie nawzajem i od nazwy programu przy pomocy odstępów: spacji lub tabulatorów.

echo raz dwa trzy

W tym przykładzie programem jest echo, a "raz dwa trzy" są trzema argumentami. W jaki sposób shell sprawdza argumenty, można sprawdzić pisząc krótki program w języku C i obserwując jego zachowanie:

#include<stdio.h>
int main(int argc, char **argv) {
int i;
for (i = 0; i < argc; i++) {
printf("Argument %d: '%s'\n", i, argv[i]);
}
}

Program zapisujemy w pliku jako args.c i kompilujemy

cc args.c -o args

A następnie uruchamiamy

maciej@matilda ~ $ ./args raz dwa trzy
Argument 0: './args'
Argument 1: 'raz'
Argument 2: 'dwa'
Argument 3: 'trzy'

Z punktu widzenia programu który uruchamiamy z shella, nie ma znaczenia, jak długie są odstępy pomiędzy argumentami:

maciej@matilda ~ $ ./args raz         dwa \
> trzy
Argument 0: './args'
Argument 1: 'raz'
Argument 2: 'dwa'
Argument 3: 'trzy'

Nawet jeżeli nasze wywołanie jest rozbite na więcej niż jedną linię, uruchomiony program widzi argumenty tak samo. Dlatego nie warto w skryptach pisać bardzo długich linii. Lepiej jest je podzielić na kilka wierszy, są wtedy czytelniejsze a maszyna i tak przetwarza je w ten sam sposób.

Cytowanie

Czasami chcemy przekazać shellowi napis i nie chcemy aby shell go interpretował. Czasami chcemy napisać dosłownie '$ZMIENNA', a nie zawartość tej zmiennej. Czasami chcemy wypisać dwie spacje pomiędzy słowami. Wtedy musimy użyć cytowania. Cytować możemy przy pomocy 'apostrofów'.

maciej@matilda:~$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:(...)
maciej@matilda:~$ echo '$PATH'
$PATH
maciej@matilda:~$ echo Tutaj napisałem pięć spacji
Tutaj napisałem pięć spacji
maciej@matilda:~$ echo 'Tutaj napisałem pięć spacji'
Tutaj napisałem pięć spacji

Zmienne

Przypisanie wartości do zmiennej

NAZWA_ZMIENNEJ=wartość

Należy zwrócić uwagę na to że nie piszemy znaku dolara przy nazwie zmiennej. Jeżeli w wartości może trafić się spacja, używamy cytowania, czyli 'apostrofów' lub "cudzysłowów".

NAZWA_ZMIENNEJ="wartość ze spacjami"

Odczytanie wartości zmiennej

Wartość zmiennej można łatwo wypisać na ekran.

echo $NAZWA_ZMIENNEJ

Kontrola przepływu

Polecenie warunkowe: if

Możemy sprawdzić, czy zmienna zawiera jakiś napis

if [ "$NAPIS" = "jakiś napis" ]
then
...komendy...
elif [ "$NAPIS" = "inny napis" ]
...inne komendy...
else
...jeszcze inne komendy...
fi

Albo sprawdzić, czy zawiera liczbę równą 101

if [ $NUMBER -eq 101 ]
then
...komendy...
fi

Tutaj trzeba uważać, bo jeżeli $NUMBER jest pusta, to nasza linijka po zastąpieniu zmiennych będzie wyglądała tak:

if [  -eq 101 ]

I polecenie test zgłosi błąd, bo nie będzie wiedziało co ma zrobić z "-eq 101 ]".

Żeby dowiedzieć się więcej o warunkach, przeczytaj man test.

Iteracja po liście: for

for ZMIENNA in <lista>
do
...komendy...
done

gdzie <lista> może być listą elementów wpisanych "na sztywno" albo tworzonych dynamicznie, np. przy pomocy lewych apostrofów.

Pętle warunkowe: while

while warunek
do
...komendy...
done

Pętla jest wykonywana tak długo, jak długo warunek jest prawdziwy. Jeżeli chcemy aby pętla wykonywała się bez końca, należy podać taki warunek, który jest cały czas prawdziwy. Dobrze nadaje się do tego komenda true, której jedynym zadaniem jest zwrócenie prawdy:

while true
do
clear
uptime
sleep 1
done

Powyższy przykład jest (z grubsza) równoważny komendzie:

watch uptime

Zwrotnica: case

case "$ZMIENNA" in
'Jakiś napis')
...komendy...
 ;;
'Jakiś inny napis')
...inne komendy...
 ;;
*)
# Gwiazdka "łapie" wszystkie napisy, które nie zostały wcześniej
# rozpoznane
...jeszcze inne komendy...
 ;;
esac

To co robi case, można również zrealizować przy pomocy poleceń if ... elif ... else ... fi, ale w przypadku kiedy mamy jedną zmienną i rozpoznajemy z góry określone możliwości, np. 'start', 'stop', 'restart', to case jest najwygodniejszą i najbardziej czytelną konstrukcją.

Sprawdzanie warunków

W poleceniach takich jak if albo while używamy wyrażeń logicznych, np.:

read -p "Podaj liczbę: " LICZBA
if [ "`expr "$LICZBA" % 2`" -eq 1 ]
then
echo "Nieparzysta"
else
echo "Parzysta"
fi

Wygląda na to że składnia pierwszej linijki jest taka:

if [ wyrażenie ]

W rzeczywistości jednak składnia pierwszej linijki wygląda tak:

if wyrażenie

Dlaczego w tym przykładzie jest nawias kwadratowy na początku? Przyjrzyjmy się mu:

maciej@matilda ~ $ which [
/usr/bin/[
maciej@matilda ~ $ ls -l /usr/bin/\[
-rwxr-xr-x 1 root root 24568 Nov 18 21:19 /usr/bin/[

Okazuje się że lewy nawias kwadratowy jest programem, który przetwarza to co ma podane w argumentach i zwraca wynik poleceniu if. Dodatkowo wymaga, żeby ostanim argumentem był prawy nawias. Polecenie [ można zastąpić równoważnym poleceniem test.

read -p "Podaj liczbę: " LICZBA
if test "`expr "$LICZBA" % 2`" -eq 1
then
echo "Nieparzysta"
else
echo "Parzysta"
fi

Przykładowe ćwiczenia z shella

Wstęp


Zaimplementuj w shellu następujący pseudokod

Zadanie 1.1

1. Dla liczb od 1 do 10 nazywanych X

  1.1. Wypisz X

Zadanie zrealizuj przy pomocy konstrukcji for ... do ... done.

Zadanie 1.2

To samo co Zadanie 1.1, tylko przy pomocy komendy while.

Zadanie 1.3

To samo co Zadanie 1.1, tylko przy pomocy komendy if.

Zadanie 1.4

To samo co Zadanie 1.1, tylko że najpierw odpytaj użytkownika o zakres. Obsłuż sytuację, kiedy użytkownik podaje liczby w niewłaściwym porządku (najpierw większą, potem mniejszą). W takiej sytuacji skrypt powinien sam domyślić się w czym problem i poprawić użytkownika; liczby mają być cały czas wyświetlane w kolejności rosnącej, od mniejszej liczby (podanej jako druga) do większej (podanej jako pierwsza).

Zadanie 1.5

  1. Weź plik lista_slow.txt z listą słów (jedno słowo w jednej linijce)
  2. Utwórz pusty plik klucze.txt
  3. Dla każdego słowa:
    1. Posortuj litery w słowie rosnąco i zapamiętaj jako KLUCZ
    2. Dopisz do pliku klucze.txt linijkę, składającą się z wartości zmiennej KLUCZ, odstępu i słowa
  4. Posortuj plik klucze.txt
  5. Wypisz grupy słów, które mają te same klucze, wynik umieść w pliku grupy.txt
  6. Usuń wszystkie grupy, które mają tylko jeden element, wynik zapisz do pliku anagramy.txt

Lista słów może być listą z zajęć, lub jakąkolwiek inną listą słów. Można użyć listy słów z projektu John The Ripper. Cała lista może być zbyt długa, więc możesz wybrać z niej ok. 1000 słów do pliku roboczego. do spisu treści


Zapisz w pseudokodzie algorytm odczytany z tego skryptu

Zadanie 2.1

for X in `seq 2 9`
do
for Y in `seq 2 9`
do
echo "$X * $Y = `expr $X * $Y`"
done
done

Zadanie 2.2

NUMBER=$RANDOM
DECIMAL=$NUMBER
while [ $NUMBER -gt 0 ]
do
BINARY=`expr $NUMBER % 2`$BINARY
NUMBER=`expr $NUMBER / 2`
done
echo "($DECIMAL)_10 = ($BINARY)_2"

Uzupełnij skrypt komentarzami

Komentarze powinny opisywać pracę skryptu.

Zadanie 3.1

echo "" > fortunes.txt
for i in `seq 10`
do
(
fortune -l $1
echo
for i in `seq 72`; do echo -n \*; done
echo
echo
) >> fortunes.txt
done

Program 'fortune' jest w pakiecie 'bsdgames' w Slackware.

Zadanie 3.2

for irq in 5 7 9 11
do
for i in 2 3
do
for j in 0 1 2 3 4 5 6 7 8 a b c d e f
do
echo -n "modprobe ne irq=$irq io=0x${i}${j}0... "
modprobe ne irq=$irq io=0x${i}${j}0 1>/dev/null 2>&1
TEST=`lsmod | grep ^ne`
echo -n "TEST=\"$TEST\" "
if [ -z $TEST ]
then
echo "Failed."
else
echo "Success!"
echo >> net.txt modprobe ne irq=$irq io=0x${i}${j}0
exit
fi
done
done
done

Znajdź i popraw błędy w następujących skryptach

Zadanie 4.1

Jest to skrypt z zadania 2.1. Czy potrafisz znaleźć błąd bez próby uruchomienia go? Skrypt oczywiście nie zadziała...

for X in `seq 2 9`
do
for Y in `seq 2 9`
do
echo "$X * $Y = `expr $X * $Y`"
done
done

Czy są możliwe takie "okoliczności przyrody" żeby ten skrypt zadziałał? Jeżeli tak, to jakie?

Zadanie 4.2

echo "Podaj swoje imię"
echo -n "> "
read NAME
if ["$NAME" = "Zenon"]
then
echo "Witaj mistrzu."
else
echo "Kto tam znowu?"
fi

Rozwiąż następujące praktyczne problemy

Zadanie 5.1

Odnajdź w bieżącym katalogu wszystkie pliki z roszerzeniem ".JPG" i wszystkie możliwe kombinacje wielkich i małych liter J/j, P/p oraz G/g; zamień je na ".jpg".

Zadanie 5.2

Napisz skrypt, którego zadaniem będzie odtworzenie filmu divx z płyty. Z punktu widzenia użytkownika skryptu powinno to wyglądać tak: "Uruchamiam skrypt i oglądam film w trybie pełnoekranowym". Proponuję użyć programu mplayer do odtworzena filmu. Skrypt w kolejnych krokach swojej pracy powinien sprawdzać, czy poprzedni krok się udał i w razie problemów wyświetlić komunikat; innymi słowy skrypt powinien mieć obsługę błędów.

Zadanie 5.3

j3ŻeLi mOż3$z 0DczY+@Ć t3n +EK5+ +0 Zn@czY żE M4$Z Z4D4TKI n@ h4x0r@. @ +0 zN@CZy, Że PrZyDA Ci $Ię tR@N$La+OR.

$ą rÓŻN3 m0Wy. J3S+ l@m3 M0W@. JE$t +3Ż h4x0R moWa. n4p15z 5kryPt, K+ÓRy 8ĘdzI3 prZer4b1@ł LaME moWę n@ h4X0r m0WĘ. poz05+@W14M dow0lNOśĆ W j@Ki 5p05ó8 Prz3R4bi4ć M0wę, t0 zn4Czy w J4ki 5P05ó8 pOD5+aw1@ć zn@Ki. n@toMi@5+ jedN4 c3CH4 SKRyptU Je$t 080W1ĄZkow@: Pod5+4wi4nI3 mU5I 8yĆ N1EdE+3rM1n15+yCZNe. niE moŻe bYć +4k ż3 +3 54me zN@k1 sĄ pod5T4w14nE z4w$ze T@K 54M0.

Jeżeli jednak czujesz się bardziej lamerem niż h4x0r3m, napisz to samo w wersji przerabiającej h4x0r mowę na lame mowę. W tej wersji podstawianie powinno być deterministyczne, natomiast postaraj się aby początek zdania był z wielkiej litery (h4x0r m0w4 nie zawiera tej informacji), reszta zdania małymi literami.

Inne materiały

Polecam dokument ze strony Witolda Paluszyńskiego omawiający programowanie w shellu. Inny dobry przegląd pracy w shellu jest w portalu linux.gery.pl. Znalazłem też jeszcze jedną stronę której tematem jest programowanie w shellu, napisaną przez Diefa.

Odpowiedzialność

Używasz przedstawionych tutaj informacji na własne ryzyko. Jeżeli pisanie skryptów w shellu spowoduje Twoje straty, w tym finansowe, nie biorę za to żadnej odpowiedzialności. Niniejszy opis przedstawia problematykę programowania w shellu w sposób wybiórczy i niepełny. Pełnej informacji szukaj w podręczniku systemowym i dokumentacji.

Autorem artykułu jest Maciej Bliziński (Automaciej). Wszystkie uwagi dotyczące tego tekstu można zgłaszać na adres automaciej@jabber.aster.pl (jabber) lub na adres mailowy:

python -c "print 'bWFjaWVqLmJsaXppbnNraUBnbWFpbC5jb20='.decode('base64')"
 

Źródło: "http://wiki.gentoo.pl/wiki/Podstawy_basha". Content is available under GNU Free Documentation License 1.2.

 

Autor: Maciej Bliziński