W wykładzie 3 przedstawimy podstawy programowania skryptów interpretera poleceń, takie jak przekazywanie danych i argumentów, operacje na zmiennych i instrukcje warunkowe.
Standardowe wyjście programu może też stać się składową polecenia. Wywołanie takiego programu
należy otoczyć odwrotnymi apostrofami (`...`
). Przed przystąpieniem do wykonania głównego
polecenia uruchamia się program otoczony takimi apostrofami, zbiera jego standardowe wyjście, dzieli na
wyrazy i wstawia w miejsce wywołania tego programu. Oto bardzo prosty, ale zarazem niezwykle
pouczający przykład:
bash$ `echo pw``echo d` /home/usr/kazio bash$ pwd /home/usr/kazio bash$ _Program
echo
po prostu wypisuje na standardowe wyjście listę swoich argumentów. Wywołanie
echo pw
powoduje więc wypisanie liter pw
, a wywołanie
echo d
powoduje wypisanie litery d
. Wyniki tych wywołań są ze sobą sklejone,
co daje słowo pwd
. Jako, że jest to pierwsze słowo, jest ono traktowane jako
program do wykonania. Program pwd
powoduje wypisanie ścieżki do bieżącego katalogu.
I właśnie on jest wykonywany.
bash$ rm `find . -name "*.txt"`
To wywołanie powoduje usunięcie wszystkich plików z rozszerzeniem txt
z całego drzewa katalogów
o korzeniu w bieżącym katalogu (.
). Program find
wyszukuje takie pliki i
wypisuje ich listę na standardowe wyjście, które następnie staję się częścią polecenia
rm
.
UWAGA: przy bardzo dużych ilościach plików do usunięcia (>32768, ale ta liczba jest zależna od
systemu) wywołanie powyższego polecenia zwróci błąd. Należy w takim wypadku użyć programu o nazwie
xagrs
, które pozwoli przekazać z góry określoną ilość argumentów do rm
.
UWAGA: takie użycie find
oraz rm
jest bardzo niebezpieczne, jeśli mamy
pliki, których nazwy zawierają spacje. Aby uczulić czytelnika na ten problem, przedstawimy
pouczający przykład. Należy go oczywiście testować w specjalnie do tego celu utworzonym
katalogu (katalogi tworzy się poleceniem mkdir
), żeby nie pousuwać sobie plików
tekstowych z komputera.
bash$ touch ala bash$ touch "ala kot.txt" bash$ ls -1 ala ala kot.txt bash$ rm `find . -name "*.txt"` rm: nie można usunąć `kot.txt': Nie ma takiego pliku ani katalogu bash$ ls -1 ala kot.txt
Poleceniem touch utworzyliśmy dwa pliki o nazwach ala
i ala kot.txt
,
następnie wypisaliśmy na terminal listę plików znajdujących się w bierzącym katalogu. Polecenie
find zwróciło następujący ciąg znaków: ./ala kot.txt
, program
rm
skasował plik ala
a następnie próbował skasować (nieistniejący)
plik kot.txt
. Prawidłowe wykonanie operacji kasowania plików z rozszerzeniem wygląda
następująco:
find . -name "*.txt" -exec rm "{}" \;
Więcej informacji na temat (bardzo praktycznego) trybu "-exec" polecenia find można znaleźć w podręczniku użytkownika polecenia find.
"./plik" jest w tym wypadku tożsame z "plik". Trzeba wiedzieć, że kropka jest interpretowana jako bierzący katalog, natomiast dwie kropki ("..") są interpretowane jako katalog nadrzędny w stosunku do bierzącego.
Na klawiaturze znajdziemy trzy znaki tego rodzaju: apostrof ('
), odwrotny apostrof
(`
), i cudzysłów ("
). Każdy z nich ma swoje znaczenie w interpreterze poleceń.
Odwrotny apostrof omówiono w punkcie Wynik instrukcji jako część polecenia. Pozostałe dwa znaki służą do przekazywania jako argumentów programów napisów, które zawierają odstępy. Polecenie:
bash$ rm duzy plikoznacza zlecenie usunięcia dwóch plików (
duzy
i plik
). Jeśli chcemy usunąć
plik o nazwie duzy plik
musimy ją otoczyć apostrofami albo cudzysłowem:
bash$ rm 'duzy plik'albo
bash$ rm "duzy plik"Apostrof i cudzysłów mają niemal takie samo znaczenie. Dzięki temu, że są dwa ich rodzaje, można je zagnieżdżać. Zagnieżdżać można jednak tylko różne rodzaje tych konstrukcji: apostrofy wewnątrz cudzysłowów i cudzysłowy wewnątrz apostrofów:
bash$ rm "ala ' ma ' kota"albo
bash$ rm 'ala " ma " kota'Zlecamy tu usunięcie jednego pliku o skomplikowanej nazwie. Natomiast:
bash$ rm "ala " ma " kota"zostanie zinterpretowane jako zlecenie usunięcia trzech plików
"ala "
(z odstępem na końcu), ma
(bez żadnych odstępów) oraz
" kota"
(z odstępem na początku).
Otoczenie napisu apostrofami albo cudzysłowami powoduje też, że interpreter nie
rozwija wzorców nazw plików występujących w tym napisie. To właśnie dlatego
w punkcie Wynik instrukcji jako część polecenia otoczono jeden z argumentów
programu find
cudzysłowami ("*.txt"
).
Otoczenie napisu apostrofami powoduje dodatkowo, że interpreter uważa napisy postaci
$VAR
za zwykły napis a nie odwołanie do zmiennej. Cudzysłowy nie mają takiej mocy i
napisy typu $VAR
są wewnątrz cudzysłowów traktowane jako odwołania do zmiennych, np.
bash$ VAR=witam bash$ echo $VAR witam bash$ ls plik1 plik2 plik3 bash$ echo $VAR * witam plik1 plik2 plik3 bash$ echo "$VAR *" witam * bash$ echo '$VAR *' $VAR * bash$ _
Taka gramatyka może być niewygodna, na przykład jeśli mamy program, który zapisuje i zwraca na terminal swój pierwszy argument i musimy przekazać skomplikowany komunikat, to możemy uzyskać na przykład coś takiego:
bash$ kotek="skacze" bash$ piesek="biega, gryzie" bash$ ./program 'zmienne "$kotek" i "$piesek" mają wartości: "'"$kotek"'", "'"$piesek"'"' zmienne "$kotek" i "$piesek" mają wartości: "skacze", "biega, gryzie" bash$ _
Używając różnych cudzysłowów można łatwo popełnić błąd.
Istnieje konstrukcja gramatyczna, która pozwala obejść problem zagnieżdżania cudzysłowów: wewnątrz
cudzysłowu (") można używać dwuznakowego symbolu \"
, który oznacza "dosłowny" cudzysłów.
Aby uzyskać dosłowny odwrotny ukośnik (\), należy wpisać go dwukrotnie. Symbol
\$
oznacza dosłowny znak dolara, czyli `echo "\$zmienna"` jest
tożsame z `echo '$zmienna'`. Używając tej konstrukcji możemy napisać:
bash$ kotek="skacze" bash$ piesek="biega, gryzie" bash$ ./program "zmienne \"\$kotek\" i \"\$piesek\" mają wartości: \"$kotek\", \"$piesek\"" zmienne "$kotek" i "$piesek" mają wartości: "skacze", "biega, gryzie" bash$ _
Apostrof (') | Cudzysłów (") | Odwrotny apostrof (`) | |
* | nie rozwija | nie rozwija | rozwija |
$ZMIENNA | nie rozwija | rozwija | rozwija |
\" | traktuje jak \" | traktuje jak " | traktuje jak \" |
Język skryptów interpretera jest pełnym językiem programowania, więc są w nim także zmienne. Nazwa zmiennej musi rozpoczynać się od litery, za którą może wystąpić ciąg liter, cyfr i podkreśleń. Nadając zmiennej wartość po prostu odwołujemy się do jej nazwy, np.
bash$ zm=wart bash$ _To oznacza nadanie zmiennej
zm
wartości wart
.
Odwołując się do wartości zmiennej, poprzedzamy jej nazwę znakiem $
, np.
bash$ echo $zm wart bash$ echo zm zm bash$ _W drugim poleceniu
zm
nie będzie traktowane jako odwołanie do
zmiennej (brak $
). To polecenie wypisze więc po prostu słowo
zm
.
bash$ zmienna="ala" bash$ echo ${#zmienna} 3 bash$ _Ilość elementów tablicy możemy uzyskać przez odwołanie ${#tab[@]}, a długość jednego elementu o indeksie $N poprzez ${#tab[$N]}.
Do wyznaczania wartości wyrażeń arytmetycznych można użyć albo konstrukcji $[ ... ]
,
albo programu expr
.
W pierwszym przypadku korzystamy z mechanizmu wbudowanego w bash
-a.
Jest on szybszy i odrobinę wygodniejszy w użyciu.
Ponieważ wiadomo, że wszystko pomiędzy $[
, a ]
jest interpretowane jak wyrażenie,
nie trzeba poprzedzać zmiennych znakiem dolara $
.
Podobnie, nie trzeba zabezpieczać *
przed potraktowaniem jak wzorzec.
bash$ echo $[1 + 2] 3 bash$ i=5 bash$ echo $i 5 bash$ i=$[i + 3] bash$ echo $i 8 bash$ i=$[i * 4] bash$ echo $i 32 bash$ _Argumenty wywołania progrmu
expr
powinny być wyrażeniem arytmetycznym, którego wartość
zostanie wypisana na standardowe wyjście, np.:
bash$ expr 1 + 2 3 bash$ i=5 bash$ echo $i 5 bash$ i=`expr $i + 3` bash$ echo $i 8 bash$ i=`expr $i \* 4` bash$ echo $i 32 bash$ _Zauważ, że w wypadku mnożenia (
*
) operator arytmetyczny
poprzedzono odwrotnym ukośnikiem (\
), ponieważ gwiazdka ma
specjalne znaczenie -- jest zastępowana przez listę plików, których nazwy
pasują do wzorca *
, czyli po prostu wszystkich plików w danym katalogu.
Poprzedzenie gwiazdki odwrotnym ukośnikiem wyłącza to jej specjalne znaczenie.
Wewnątrz skryptu czasem trzeba przeczytać coś ze standardowego wejścia.
Do tego celu służy polecenie read
, które
czyta jedną linię ze standardowego wejścia, dzieli ją na słowa i przypisuje
kolejne słowa do zmiennych, których nazwy są argumentami wywołania read
.
W ostatniej zmiennej umieszczana jest reszta, tzn. wszystkie słowa, których nie
przypisano do poprzednich zmiennych. Wywołanie:
read a b cPowoduje odczytanie jednej linii ze standardowego wejścia skryptu i przypisanie jej pierwszego słowa na zmienną
a
, drugiego słowa na zmienną
b
, a pozostałych słów (trzeciego, czwartego etc.) na zmienną
c
. Przypuśćmy, że linię Ala ma kota, piszą na płotach
podano na standardowe wejście następującego skryptu.
read a b c echo a = $a echo b = $b echo c = $cSkrypt wypisze wówczas:
a = Ala b = ma c = kota, piszą na płotach
Każdy program ma środowisko, które składa się ze zmiennych i ich wartości.
Owo środowisko jest przekazywane do wszystkich uruchamianych przez ten program
programów. Program potomny ma dokładnie takie same środowisko jak program
macierzysty. Przykładami takich zmiennych środowiskowych są PATH
(lista katalogów, w których należy szukać programów) i HOME
(katalog domowy bieżącego użytkownika).
To samo dotyczy interpretera poleceń. Pewna cześć jego zmiennych należy do środowiska i jest przekazywana programom uruchamianym przez interpreter. Działanie programu może zależeć od wartości tych zmiennych. Zwykłe zmienne nie są przekazywane programom potomnym, zmienne środowiskowe są.
Aby wskazać, że zmienna jest środowiskowa, należy przypisanie do niej poprzedzić
słowem export
, albo wydać polecenie export
z jej nazwą jako
argumentem, np.
bash$ MYPROG_HOME=/usr/home/myprog bash$ export MYPROG_HOME bash$ _albo
bash$ export MYPROG_HOME=/usr/home/myprog bash$ _Jeśli teraz wywołamy
bash
to będziemy mogli odczytać w nim wartość
zmiennej MYPROG_HOME
. Gdybyśmy nie użyli słowa export
,
w potomnym interpreterze wartość tej zmiennej byłaby pusta:
bash$ MYCONFIG=/usr/home/kazio/conf.txt bash$ bash bash$ echo $MYCONFIG bash$ exit bash$ export MYCONFIG bash$ bash bash$ echo $MYCONFIG /usr/home/kazio/conf.txt bash$ exit bash$ _
W wywołaniu skryptu można podać argumenty, np.
skrypt1 ala ma kotaDo tych argumentów można się w skrypcie odwoływać poprzez symbole.
$1
, $2
, ..., $9
, których wartościami
są odpowiednio pierwszy, drugi, ..., dziewiąty argument skryptu. Kolejne argumenty można
uzyskać poprzez użycie nieco innego symbolu, np. dla argumentu dwudziestego będzie to ${20}.
Dodatkowo:
$0
to nazwa, za której pomocą wywołano skrypt,
$#
to liczba argumentów skryptu,
$@
to wszystkie argumenty skryptu.
shift
. Powoduje ono:
$#
o jeden.
$1
.
$2
do $1
(teraz drugi argument
jest dostępny przez $1
).
$3
do $2
.
$9
do $8
.
$9
... itd.
echo Program $0 wywołano z $# argumentami echo -- pierwszy: $1 echo -- drugi: $2 echo -- trzeci: $3 echo -- wszystkie: $@ shift echo Program $0 wywołano z $# argumentami echo -- pierwszy: $1 echo -- drugi: $2 echo -- trzeci: $3 echo -- wszystkie: $@Gdy wywołamy go za pomocą polecenia:
skrypt1 ala ma kota
otrzymamy w wyniku:
Program skrypt1 wywołano z 3 argumentami -- pierwszy: ala -- drugi: ma -- trzeci: kota -- wszystkie: ala ma kota Program skrypt1 wywołano z 2 argumentami -- pierwszy: ma -- drugi: kota -- trzeci: -- wszystkie: ma kotaPoleceniu
shift
można podać argument. Wywołanie shift n
oznacza n
-krotne wykonanie shift
.
Napisać skrypt wywoływany w następujący sposób:
nty arg1 arg2 arg3 ...Ma on wczytać ze standardowego wejścia liczbę
n
i wypisać
swój n
-ty argument.
Rozwiązanie.
Interpreter umożliwia też wykonywanie instrukcji warunkowej. Oto jej składnia:
if warunek1; then instrukcje1 elif warunek2; then instrukcje2 ... else instrukcje_else fiFrazy
elif
i else
są opcjonalne. Liczba fraz
elif
może być dowolna. Jeśli prawdziwy jest warunek1
,
to wykonywane są instrukcje1
, w przeciwnym przypadku
(warunek1
jest fałszywy), jeśli prawdziwy jest warunek2
,
to wykonywane są instrukcje2
itd. Jeśli żaden z warunków nie jest
prawdziwy, to wykonuje się instrukcje_else
.
W wykładzie 3 przedstawiliśmy podstawowe narzędzia programistyczne interpretera poleceń, co pozwoli nam na pisanie skryptów o podstawowej funkcjonalności.
$1
, $2
, ..., $9
,
$@
i $#
.
n
-ty parametr i wypisuje stosowną informację.