W systemie typu Unix jest podział na użytkowników i grupy. Każdy użytkownik może przynależeć do kilku grup. Do wyświetlania przynależności do grup służy polecenie groups.
bashtest@host:~$ groups users bashtest@host:~$ groups bashtest root kubus bashtest : users root : root kubus : users cdrom floppy audio src video staff bashtest@host:~$
Bez argumentów wyświetla przynależność do grup aktualnego użytkownika. Z argumentami przynależność do grup podanych użytkowników. Na przykład użytkownik kubus przynależy do większej ilości grup, co daje mu większe prawa w systemie.
Każdy plik/katalog należy do dokładnie jednego użytkownika i grupy. Z każdym plikiem/katalogiem związane są trzy rodzaje praw dostępu:
r
prawo do odczytu,
w
prawo do modyfikacji (czyli do zapisu, bądź usunięcia),
x
prawo do uruchomienia; w przypadku katalogu oznacza to prawo do zmiany bieżącego katalogu na ten katalog.
Prawa dostępu przydzielane są trzem kategoriom użytkowników:
Aby wyświetlić informacje o właścicielach i prawach dostępu możemy użyć polecenia ls z opcją -l:
bashtest@host:~$ ls -l razem 128 drwx------ 2 bashtest users 4096 2006-07-08 09:37 Mail d-wx--x--x 2 bashtest users 4096 2006-08-07 15:28 niedostępny_katalog ----rw---- 1 bashtest users 5 2006-08-07 15:30 plik_dla_pozostałych_userów -rwxr-xr-x 1 root root 109552 2006-08-07 15:32 program -rw-r--r-- 1 bashtest users 13 2006-08-01 15:18 test.txt bashtest@host:~$
Po lewej stronie są prawa dostępu. Literka po lewej mówi o typie pliku, kolejne trzy literki pokazują prawa dostępu dla pierwszej kategorii użytkowników, kolejne trzy o drugiej kategorii użytkowników i ostatnie trzy literki o ostatniej kategorii. W trzeciej i czwartej kolumnie pokazany jest użytkownik i grupa do której należy dany plik/katalog.
Mail i niedostępny_katalog
są katalogami (literka d po lewej). Katalog Mail
jest dostępny tylko dla użytkownika bashtest (2-4 literki rwx
oznaczają ustawione wszystkie prawa dostępu: do odczytu, zapisu i uruchamiania). Katalog niedostępny_katalog
nie ma ustawionych praw do odczytu, zatem nie można wyświetlić jego zawartości, ale można zmienić na niego bieżący katalog, gdyż ma ustawione prawa do uruchomienia. Plik plik_dla_pozostałych_userów
mogą odczytywać i modyfikować tylko użytkownicy inni niż bashtest należący do grupy users. Plik program
jest programem i można go uruchamiać.
Do zmiany właściciela służą polecenia chown
i chgrp
. Do zmiany praw dostępu służy polecenie chmod.
hello_world.sh
o następującej zawartości:
#!/bin/sh echo "Hello world"
Rozszerzenie sh jest standardowym rozszerzeniem skryptów napisanych w bashu. Nie jest ono konieczne, ale dobrze by było, żeby już sama nazwa pliku mówiła nam o jego typie. Pierwsza linijka jest podpowiedzią dla systemu, jak ma być uruchomiony ten plik. System użyje polecenia /bin/sh
do interpretacji tego pliku.
Spróbujmy uruchomić ten plik.
bashtest@host:~$ hello_world.sh bash: hello_world.sh: command not found bashtest@host:~$
Takie polecenie nie zostało znalezione. System szuka danego polecenia wśród wszystkich katalogów zapamiętanych na zmiennej środowiskowej PATH
. Zmienna środowiskowa jest to taka zmienna, która została zdefiniowana zanim jeszcze uruchomiliśmy interpreter. Własne zmienne środowiskowe, które zostaną przekazane programom przez nas uruchomionych można definiować za pomocą komendy export
. Zobaczmy, co zawiera zmienna PATH
.
bashtest@host:~$ echo $PATH /usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games bashtest@host:~$
Jak widzimy, nie zawiera ona bieżącego katalogu, w którym znajduje się nasz skrypt. Przy uruchamianiu polecenia, które nie znajduje katalogu podanym w PATH
, trzeba podawać również ścieżkę (względną, bądź bezwzględna) przed nazwą pliku. W tym przypadku musimy podać katalog bieżący, co najprościej można zrobić przy użyciu kropki.
bashtest@host:~$ ./hello_world.sh bash: ./hello_world.sh: Brak dostępu bashtest@host:~$
Tym razem dostaliśmy komunikat o złych prawach dostępu. Zobaczmy:
bashtest@host:~$ ls -l hello_world.sh -rw-r--r-- 1 bashtest users 29 2006-08-07 15:45 hello_world.sh bashtest@host:~$
Ten plik nie ma ustawionych praw do uruchamiania. Możemy to zrobić używając polecenia chmod. Aby ustawić prawa uruchamiania tylko dla użytkownika bashtest, możemy użyć opcji u+x
. Jeśli chcemy ustawić prawa uruchamiania dla wszystkich, używamy opcji a+x
. W tym przypadku ustawimy prawa uruchamiania tylko dla nas.
bashtest@host:~$ chmod u+x hello_world.sh bashtest@host:~$ ls -l hello_world.sh -rwxr--r-- 1 bashtest users 29 2006-08-07 15:45 hello_world.sh bashtest@host:~$
Teraz wygląda lepiej spróbujmy uruchomić nasz skrypt.
bashtest@host:~$ ./hello_world.sh
Hello world
bashtest@host:~$
Udało się!
Komentarze zaczynają się od symbolu #
. Wszystkie pozostałe znaki aż do końca linii są ignorowane. W pierwszej linii skryptu helo_world.sh
mamy już taki komentarz, który jest zarazem informacją dla systemu. Dodajmy jeszcze dwa komentarze.
#!/bin/sh # Przykładowy skrypt wypisujący napis "Hello world" echo "Hello world" # Tutaj wypisujemy co trzeba
Skrypty - podobnie jak dowolne programy - możemy uruchamiać podając im argumenty. Następujące zmienne o specjalnych nazwach pozwalają odczytywać argumenty:
$# |
zwraca liczbę argumentów, |
$0 |
zwraca nazwę pliku bieżącego programu, |
$1 , $2 , ... |
zwraca odpowiednio pierwszy argument, drugi argument, itd., |
$@ |
rozwija się do listy wszystkich argumentów; przydatne jeśli chcemy przekazać wszystkie argumenty innemu programowi. Jeśli chcemy mieć pewność, że każdy argument będzie osobnym słowem, należy użyć cudzysłowów: "$@" ; ma to znaczenie na przykład wtedy, gdy istnieje argument, który zawiera spację.
|
Aby operować na dalszych argumentach pomocne jest polecenie shift
, które usuwa pierwszy argument, a pozostałe przesuwa o jeden w lewo. Aby n-krotnie wywołać polecenie shift
wystarczy podać mu to n jako argument: shift n
.
Na przykład dla skryptu test_arg.sh
o zawartości
#!/bin/sh # Testowanie argumentów echo "Uruchomiłeś program `basename $0`" echo Wszystkie: $@ echo "Pierwsze trzy: '$1', '$2', '$3'" shift 2 echo "shift 2" echo "Wszystkie: $@" echo "Pierwsze trzy: '$1', '$2', '$3'"
mamy efekt
bashtest@host:~$ ./test_arg.sh Raz Dwa "To jest zdanie" Cztery Uruchomiłeś program test_arg.sh Wszystkie: Raz Dwa To jest zdanie Cztery Pierwsze trzy: 'Raz', 'Dwa', 'To jest zdanie' shift 2 Wszystkie: To jest zdanie Cztery Pierwsze trzy: 'To jest zdanie', 'Cztery', '' bashtest@host:~$
Jak w każdym liczącym się języku, w bashu możemy wyliczać wartości wyrażeń arytmetycznych. Możemy zrobić to na kilka sposobów.
Najprostszym sposobem jest użycie polecenia expr. Trzeba przy tym pamiętać, żeby osobne tokeny (tzn. liczby i operatory arytmetyczne) były podawane w osobnych argumentach. Wynika to stąd, że expr potrafi też operować na łańcuchach znakowych (czym się nie będziemy w tej chwili zajmować), więc musi jakoś te łańcuchy dostawać, a jedyną droga to przez argumenty.
Dostępnych jest pięć operatorów arytmetycznych:
Ponadto możemy wykonywać porównania <, <=, =, == (synonim =), !=, >=, >. W wyniku mamy 1, gdy relacja jest spełniona i 0 w przeciwnym przypadku.
Trzeba też pamiętać by znaki specjalne poprzedzać backslashem lub brać w cudzysłowy. Przykłady:
bashtest@host:~$ expr 2\*3 2*3 bashtest@host:~$ expr 2 \* 3 6 bashtest@host:~$ expr '2 * 3' 2 * 3 bashtest@host:~$ expr 2 \* \(7 - 1\) expr: argument nieliczbowy bashtest@host:~$ expr 2 \* \( 7 - 1 \) 12 bashtest@host:~$ a=5 bashtest@host:~$ a=`expr $a + 1` bashtest@host:~$ echo $a 6 bashtest@host:~$ expr 3 \<= 4 1 bashtest@host:~$ expr 3 '<=' 1 0 bashtest@host:~$
Znacznie wygodniejszą formą pisania wyrażeń jest forma $(( wyrażenie ))
. W stosunku do expr
w ciapkach ma prawie same zalety. Jest jeden problem, ta składnia może nie działać w innych shellach, czy w starszych wersjach Basha (ale kto teraz używa czegoś innego niż Bash). Pierwszą zaletą jest szybkość, tzn. użycie tej składni nie powoduje tworzenia nowego procesu (co ma miejsce w przypadku `expr ...`)
i jest interpretowane bezpośrednio przez Basha. Po drugie przy odwoływaniu się do zmiennych nie musimy poprzedzać ich znakiem $
, gdyż każdy identyfikator wewnątrz podwójnych nawiasów jest traktowany jak zmienna. Nie musimy także dbać o używanie odstępów i backslashowania znaków specjalnych. Trzecią zaletą jest bogatsza paleta operatorów arytmetycznych. Otóż wyrażenia arytmetyczne mogą tu zawierać dowolne operatory, które można znaleźć w języku C, np. inkrementacje/dekrementacje zmiennych (ID++, --ID)
, operacje bitowe (<<, &, ~), przypisania arytmetyczne (=, +=, *=), itp. Więcej o znaczeniu tych operacji i dozwolonych działaniach można znaleźć w kursie języka C lub w dokumentacji Basha.
Składni (( wyrażenie ))
używamy wtedy, gdy nie potrzebujemy wyniku, czyli wtedy, gdy wyrażenie nie jest częścią instrukcji, tylko jest sama w sobie instrukcją. Najlepiej będzie, jak przyjrzymy się przykładom.
Kilka sposobów na zwiększenie zmiennej o 1:
bashtest@host:~$ a=0 bashtest@host:~$ a=$((a + 1)) bashtest@host:~$ ((a=a+1)) bashtest@host:~$ ((a++)) bashtest@host:~$ ((a += 1)) bashtest@host:~$ echo $a 4 bashtest@host:~$
Inne przykłady:
bashtest@host:~$ echo "1 + ... + $x = $((x * (x + 1) >> 1))" 1 + ... + 5 = 15 bashtest@host:~$ echo $((x++)) 5 bashtest@host:~$ echo $((++x)) 7 bashtest@host:~$ echo $((x += x > 0)) 8 bashtest@host:~$ echo "x = $x" x = 8 bashtest@host:~$
let
jest wbudowanym poleceniem Basha i używamy go, podając mu jako argumenty wyrażenia do przetworzenia.
let wyrażenie1 wyrażenie2 ...
równoważne jest ciągowi poleceń
((wyrażenie1)) ((wyrażenie2)) ...
Przykład:
bashtest@host:~$ x=0 bashtest@host:~$ let x+=2 "x += 4" bashtest@host:~$ echo $x 6 bashtest@host:~$
Trzeba pamiętać, że wyrażenie zawierające odstępy trzeba ujmować w cudzysłowy, aby formowały jeden argument.
W skryptach czasami jest potrzeba wczytania czegoś ze standardowego wejścia. Możemy chcieć pobrać od użytkownika jakąś informację. Możemy też chcieć wczytywać standardowe wejście i stopniowo je przetwarzać. Do tych celów jest polecenie read
.
read
wywołane bez argumentów wczytuje jedną linię ze standardowego wejścia na zmienną o nazwie REPLY
. Jeśli podamy jeden argument, read,/code> wczyta tą linię na zmienną o nazwie takiej samej, jak zawartość argumentu. Jeśli podamy więcej argumentów reprezentujących nazwy zmiennych, <code>read
na pierwsze zmienne będzie wczytywał pojedyncze słowa, a na ostatnią wczyta pozostałość bieżącej linii do jej końca. Prześledźmy to na przykładzie.
Dla skryptu
#!/bin/sh read echo $REPLY read a echo $a read a b c echo "a='$a', b='$b', c='$c'" read x echo "'$x'"
i dla wejścia
Pierwsza linia (pamiętać o cudzysłowach przy odwoływaniu się do $REPLY) Druga linia (teraz pamiętamy - "$a") Raz Dwa Trzy Cztery Czwarta linia jest pusta, a to jest piąta linia
otrzymamy wynik
Pierwsza linia (pamiętać o cudzysłowach przy odwoływaniu się do $REPLY) Druga linia (teraz pamiętamy - "$a") a='Raz', b='Dwa', c='Trzy Cztery'
Gdy wykonujemy polecenie, czasami chcemy zadać mu konkretne wejście. Możemy to zrobić na przykład za pomocą komendy echo:
echo "Nasze wejście" | polecenie
Użycie echo
dla wejść, które mają składać się z wielu linii jest jednak kłopotliwe. W tym celu w Bashu jest możliwość podania fragmentu skryptu jako wejście do polecenia. Służy do tego symbol specjalny <<. Takie "przekierowanie" << SŁOWO
mówi, że wejście dla uruchamianego polecenia ma być czytane z aktualnego wejścia tak długo, aż zostanie napotkany napis SŁOWO
. Na przykład wynikiem skryptu
#!/bin/sh echo "Moje ulubione liczby:" sort -n << LICZBY 120 10 2006 314159 0 LICZBY echo "Od najmniejszej do największej, rzecz jasna"
jest
Moje ulubione liczby: 0 10 120 2006 314159 Od najmniejszej do największej, rzecz jasna
Każdy program po ukończeniu zwraca swój kod wyjścia. Można go pobrać używając specjalnej zmiennej $
?.
bashtest@host:~$ ls *.txt test.txt bashtest@host:~$ echo $? 0 bashtest@host:~$ ls *.nieznane ls: *.nieznane: Nie ma takiego pliku ani katalogu bashtest@host:~$ echo $? 2 bashtest@host:~$
Zgodnie z konwencją jeśli polecenie wykonało się z sukcesem, kodem wyjścia jest 0, a jeśli w wyniku wykonania pojawiły się błędy lub polecenie skończyło się porażką, zwracany jest kod różny od zera.
Normalnie własny skrypt kończy się ze statusem wyjścia równym zero. Możemy zakończyć skrypt w dowolnym miejscu z wybranym przez nas statusem wyjścia, stosując polecenie exit. Na przykład instrukcja exit 1 powoduje natychmiastowe zakończenie skryptu z kodem wyjścia 1.
Instrukcja if
w najprostszej postaci ma następującą składnię:
if polecenie_warunek; then instrukcje fi
Jej działanie jest następujące. Wykonywane jest polecenie polecenie_warunek
. Jeśli kod wyjścia tego polecenia jest 0, wykonywane są instrukcje między then, a fi
. Jeśli kod wyjścia polecenia był niezerowy, wykonywanie instrukcji if
jest zakończone i interpreter przechodzi do wykonywania instrukcji znajdujących się po słowie kluczowym fi
.
Widzimy, że rolę warunków logicznych spełniają tu po prostu zwykłe polecenia, a prawda lub fałsz jest to odpowiednio status wyjścia równy zero lub status wyjścia różny od zera.
Składnia if
z użyciem else
:
if polecenie_warunek; then instrukcje1 else instrukcje2 fi
Jeśli warunek jest prawdziwy, wykonywane są instrukcje1
, w przeciwnym razie wykonywane są instrukcje2
. Przykład:
if cd $katalog; then echo "Jesteśmy w katalogu $katalog" else echo "Nie udało się wejść do katalogu $katalog" fi
Pełna składnia if jest następująca:
if warunek1; then instrukcje1 elif warunek2; then instrukcje2; ... else instrukcje_else; fi
Część z else
jest opcjonalna. instrukcje1
są wykonane, jeśli jest spełniony warunek1
, w przeciwnym razie, jeśli jest spełniony warunek2
, to wykonywane są instrukcje2
, itd. Na końcu, jeśli żaden warunek nie jest spełniony, wykonywane są instrukcje_else.
Powstaje pytanie, jak tworzyć polecenia, które sprawdzają jakieś sensowne warunki np. porównywanie liczb. Do tego celu służy polecenie test
. Potrafi ono porównywać łańcuchy znakowe, liczby i sprawdzać istnienie plików.
Jeśli chodzi o porównywanie łańcuchów znakowych, mamy następujące możliwości. -z ŁAŃCUCH
sprawdza, czy długość łańcucha jest równa zero, a -n ŁAŃCUCH
, sprawdza, czy długość łańcucha jest różna od zera. Ponadto możemy porównywać dwa łańcuchy np. ŁAŃCUCH1
< ŁAŃCUCH2
. Porównanie jest leksykograficzne. Możliwe operatory to ==, !=, <, >.
Do porównywania dwóch liczb są inne operatory: -eq, -ne, -lt, -le, -gt, -ge, których odpowiedniki matematyczne to =, <>, <, <=, >, >=.
Na przykład poniższe polecenia zwrócą prawdę (tj. status wyjścia równy 0):
test -z "" test abc \< def test 3 \> 17 test 3 -lt 17
Można także sprawdzać istnienie i typ plików, na przykład:
if test -a $plik; then echo "$plik istnieje" if test -f $plik; then echo "$plik jest zwykłym plikiem" elif test -d $plik; then echo "$plik jest katalogiem" fi fi
Polecenie
test warunek
można też pisać w postaci
[ warunek ]
Taka forma jest po prostu wygodniejsza.
Warto wiedzieć, że instrukcja arytmetyczna (( ... ))
też zwraca status. Zwraca 0, jeśli wartość wyrażenia jest niezerowa, i zwraca 1, jeśli wartość wyrażenia wynosi 0. Pozwala to w Bashu stosować w bardzo wygodny sposób porównania, dokładnie tak samo jak w C.
bashtest@host:~$ if (( 0 )); then echo prawda; else echo fałsz; fi fałsz bashtest@host:~$ if (( 1 )); then echo prawda; else echo fałsz; fi prawda bashtest@host:~$ if (( 3 < 4 )); then echo prawda; else echo fałsz; fi prawda bashtest@host:~$ if (( 0 < -1 )); then echo prawda; else echo fałsz; fi fałsz bashtest@host:~$ if (( 3 * 6 - 2 * 9 )); then echo prawda; else echo fałsz; fi fałsz bashtest@host:~$ if (( 1/0 )); then echo prawda; else echo fałsz; fi bash: ((: 1/0 : division by 0 (error token is " ") fałsz bashtest@host:~$
Najprostszymi poleceniami, które zwracają prawdę i fałsz, prostszymi nawet niż (( 1 )) i (( 0 )) są true
i false
, co przydaje się na przykład w pętlach.