Systemy operacyjne ZSOP. Wykład 3

Michał Goliński

2017-11-26

Wstęp

Pojęcia – przypomnienie

Ostatnio poznaliśmy pojęcia:

  • cykl życia procesu
  • scheduler i kilka możliwych algorytmów
  • urządzenie znakowe
  • stdin, stdout, stderr
  • potok

Polecenia – przypomnienie

Poznaliśmy również polecenia działające na zawartości plików:

  • echo
  • cat, tac, od, nl, base64
  • head, tail, split, csplit
  • wc, md5sum, etc.
  • sort, shuf, uniq
  • cut, paste, join
  • tr
  • grep, sed
  • tee, pv
  • tar, gzip, xz, etc.

Pytania?

Plan na dziś

  • Zmienne w powłoce
  • Instrukcja warunkowa i pętle
  • Podstawianie i cytowanie
  • Definiowanie funkcji
  • Przekazywanie argumentów
  • Skrypty i shebang
  • Program make

Zmienne w powłoce

Zmienne powłoki

  • Zmienne w bash-u tworzymy przez nadanie im wartości (być może pustej).
  • Powłoka rozróżnia wielkość znaków w nazwach zmiennych.
  • Wszystkie zmienne są typu tekstowego.
  • Do wartości zmiennych odwołujemy się poprzedzając nazwę zmiennej znakiem $.
  • Nazwy zmiennych mogą składać się z liter, cyfr i znaku podkreślenia. Nie mogą zaczynać się od cyfry.
  • Odwołanie się do wartości nieistniejącej zmiennej nie jest błędem, daje pustą wartość.

Przykład

$ zmienna=1
$ _var=2
$ ZMIENNA=
$ echo $zmienna $_var $ZMIENNA

Polecenie unset

Aby usunąć zmienną ze zdefiniowanych zmiennych powłoki, możemy użyć polecenia unset:

$ zmienna=1
$ echo $zmienna
$ unset zmienna
$ echo $zmienna

W praktyce najczęściej nie ma dużej różnicy pomiędzy usunięciem zmiennej a nadaniem jej pustej wartości.

Wartość zmiennej z klawiatury

Zmienna może przyjąć wartość wpisaną przez użytkownika z klawiatury, co może być przydatne przy pisaniu skryptów. Służy do tego polecenie read, któremu przekazujemy nazwę zmiennej:

$ read name
Michał
$ echo Hello ${name}!

Zmienne środowiskowe

Normalnie zmienne powłoki nie są widoczne dla programów uruchamianych przez tę powłokę. Zmienną powłoki można jednak promować do tzw. zmiennej środowiskowej, która jest przekazywana do uruchamianych programów. Służy do tego polecenie export:

$ zmienna=1
$ export zmienna
$ export inna_zmienna=2

Zmiana wartości zmiennej jest automatycznie widoczna (nie trzeba ponownie eksportować).

Jednorazowa zmienna środowiskowa

Jeżeli nie chcemy zmieniać wartości zmiennej środowiskowej na stałe, ale chcemy uruchomić program ze specjalnie dla niego ustawioną wartością możemy zrobić to następująco:

$ zmienna=1 program

Zmienna środowiskowa PATH

Zmienna środowiskowa PATH zawiera listę katalogów porozdzielanych dwukropkami. Katalogi te są przeszukiwane w celu znalezienia uruchamianych programów.

$ echo $PATH
/opt/texlive/2017/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/home/michal/.local/bin:/home/michal/bin

Nie można dodać katalogu z dwukropkiem w nazwie do tej listy (w razie konieczności można posłużyć się łączem symbolicznym).

Zmienna środowiskowa PS1

Zmiana wartości tej zmiennej pozwala zmieniać wygląd i informacje wyświetlane jako znak zachęty.

Zmienna środowiskowa IFS

Zmienna przechowuje znaki które chcemy uznawać za oddzielające poszczególne słowa czy argumenty (domyślnie: spacja, tabulator, nowa linia).

$ echo "$IFS" | od -c

Zmienna środowiskowa LANG

Wartość tej zmiennej wiele programów wykorzystuje aby wyświetlać informacje w języku wybranym przez użytkownika. Może też wpływać na inne zachowania, np. sort używa wartości tej zmiennej do definiowania porządku sortowania.

$ echo $LANG
en_US.UTF-8
$ man bash
$ LANG=pl_PL.UTF-8 man bash

Sygnalizowanie sukcesu polecenia

Kod wyjścia

Każdy program w systemach Uniksowych zwraca przy zakończeniu działania liczbę z przedziału \([0, 255]\). Liczba ta to tak zwany kod wyjścia. Zgodnie z konwencją:

  • 0 sygnalizuje poprawne zakończenie działania.
  • dodatnie wartości sygnalizują zakończenie z błędem (np. podany plik nie istnieje)
  • wartości większe od 128 sygnalizują zakończenie z powodu przechwycenia sygnału

Kod wyjścia cd.

Znaczenie konkretnych kodów wyjścia jest różne dla różnych programów. Czasem bardzo trudno ustalić co oznaczają poszczególne kody wyjścia, chociaż z reguły interesuje nas tylko rozróżnienie: poprawne/niepoprawne.

Listy poleceń

Lista poleceń

Lista poleceń to ciąg poleceń wraz argumentami (także potoków), porozdzielanych znakami ;, &, && i ||. Lista może kończyć się znakiem ;, & lub znakiem nowej linii. Średniki mogą być zastąpione znakami nowej linii. Znaczenie znaków jest następujące:

  • ; – polecenia są wykonywane kolejno, następne polecenie czeka na zakończenie poprzedniego. Kodem wyjścia jest kod ostatnio wykonanego polecenia.
  • & – polecenie jest wykonywane w podpowłoce w tle. Kodem wyjścia jest 0.
  • && i || – pozwalają na wykonanie warunkowe

Wykonanie warunkowe z &&

Przy wykonywaniu listy poleceń:

$ polecenie1 && polecenie2

polecenie2 będzie wykonane tylko jeśli kod wyjścia z polecenia1 będzie równy 0 (tzn., gdy polecenie1 się powiedzie).

Wykonanie warunkowe z ||

Przy wykonywaniu listy poleceń:

$ polecenie1 || polecenie2

polecenie2 będzie wykonane tylko jeśli kod wyjścia z polecenia1 będzie różny od 0 (tzn., gdy polecenie1 się nie powiedzie).

Polecenia złożone

Grupowanie poleceń

Wywołanie

$ { lista poleceń }

spowoduje wykonanie listy poleceń w bieżącej powłoce, podczas gdy wykonanie

$ ( lista poleceń )

spowoduje wykonanie listy poleceń w podpowłoce.

Wykonanie w podpowłoce

Wiele poleceń przy programowaniu strukturalnym w bash-u wykonuje się w tzw. podpowłoce. Znaczy to, że powłoka uruchamia proces-dziecko będący jej kopią i dopiero ta kopia wykonuje polecenie. Niesie to kilka konsekwencji:

  • Polecenia w podpowłoce nie mogą zmienić wartości zmiennych powłoki.
  • Polecenia w podpowłoce nie mogą zamknąć wywołującej powłoki.

Pętla for

Pętla for ma następującą składnię:

$ for zmienna in val1 val2 val3...; do lista poleceń; done

Spowoduje to wykonanie listy poleceń, gdzie zmienna $zmienna będzie przyjmowała kolejno wartości val1, val2, val3. Jak widać, pętla for w bash-u odpowiada bardziej pętlom foreach w niektórych językach programowania. Podobnie jak w wielu językach, polecenia break i continue odpowiednio przerywają pętlę i przerywają obieg pętli. Pętlę najczęściej zapisujemy w kilku linijkach, wtedy możemy pominąć średniki.

Polecenie seq

Z pętlą for przydatne bywa poleceni seq, które zwraca kolejne liczby z postępu arytmetycznego:

$ seq 1 20
$ seq 1 5 20
$ seq -w 1 99

Pętla while

Pętla while ma następującą składnię:

$ while warunek; do lista poleceń; done

Spowoduje to wykonywanie listy poleceń tak długo dopóki polecenie warunek zwraca kod powrotu 0. Podobnie jak w wielu językach, polecenia break i continue odpowiednio przerywają pętlę i przerywają obieg pętli. Pętlę najczęściej zapisujemy w kilku linijkach, wtedy możemy pominąć średniki.

Instrukcja warunkowa

Instrukcja warunkowa ma następującą składnię:

$ if warunek
then
lista poleceń 1
else
lista poleceń 2
fi

Jeśli polecenie warunek1 daje kod powrotu 0, wykonana będzie lista poleceń 1, w przeciwnym przypadku lista poleceń 2. Część z else można pominąć.

Instrukcja wyboru

Instrukcja wyboru pozwala wykonać pewne polecenia w zależności od tego czy wartość wyrażenia pasuje do podanych szablonów. Składnia jest następująca:

$ case wyrażenie in
szablon1) lista poleceń 1;;
szablon2) lista poleceń 2;;
esac

Szablony w instrukcji wyboru działają na tej samej zasadzie jak szablony nazw plików w powłoce.

Wyrażenia warunkowe

Wprowadzenie

W pętli while i w instrukcji warunkowej if pojawiają się polecenia rozumiane jako warunki logiczne. Teoretycznie można wstawiać tam dowolne polecenia, ale powłoka bash dysponuje przydatnym narzędzie do testowania najczęściej pojawiających się warunków logicznych.

Polecenia [ i [[

Kiedyś polecenie [ było naprawdę osobnym programem, inną nazwą dla zewnetrznego programu test, dzisiaj najczęściej oba są wbudowanymi poleceniami powłoki. Niestety z powodów historycznych muszą być wstecznie zgodne z ww. poleceniem. Polecenie wbudowane [[ nie ma tych ograniczeń, ale może nie być rozumiane w powłokach innych niż bash. Podobne konstrukcje nazywamy bash-yzmami. W nowych skryptach na własny użytek prawdopodobnie należy używać [[.

$ [[ a < b ]]
$ [ a < b ]
$ [[ 1 == 1 || 2 == 2 ]]
$ [ 1 = 1 || 2 == 2 ]

Operatory logiczne

  • ! – negacja
  • && – koniunkcja
  • || – alternatywa

Najważniejsze testy logiczne

  • -e plik – prawda gdy plik istnieje
  • -d plik – prawda gdy plik istnieje i jest katalogiem
  • -f plik – prawda gdy plik istnieje i jest zwykłym plikiem
  • -r plik – prawda gdy plik istnieje i można go odczytać
  • -s plik – prawda gdy plik istnieje i ma niezerową długość
  • plik1 -nt plik2 – prawda gdy plik1 jest nowszy niż plik2 (lub plik2 nie istnieje)

Najważniejsze testy logiczne cd.

  • -z napis – prawda gdy napis jest zerowej długości
  • -n napis – prawda gdy napis jest niezerowej długości
  • napis1 == napis2, napis1 != napis2 – prawda gdy napisy są odpowiednio równe i różne
  • napis == szablon – sprawdza czy napis pasuje do szablonu
  • napis =~ regexp – sprawdza czy napis pasuje do wyrażenia regularnego
  • napis1 < napis2 – prawda gdy napis1 jest leksykograficznie wcześniej niż napis2

Porównywanie liczb

Powłoka potrafi też porównywać liczby, służą do tego poniższe operatory:

  • -eq – równe
  • -ne – nie równe
  • -lt – mniejsze
  • -le – mniejsze lub równe
  • -gt – większe
  • -ge – większe lub równe
$ [[ 11 < 2 ]]
$ [[ 11 -lt 2 ]]

Operacje arytmetyczne

Obliczenia

Powłoka pozwala prowadzić również obliczenia. Służy do tego polecenie let oraz (częściej) operator ((.

Wewnątrz wyrażeń arytmetycznych powłoka rozumie wszystkie operatory arytmetyczne języka C, z tym, że powłoka prowadzi obliczenia tylko na liczbach stałoprzecinkowych o stałej długości. Długość zależy od architektury komputera, na 64-bitowych systemach operacyjnych są to liczby 64-bitowe ze znakiem.

Operatory arytmetyczne

  • ++, --
  • +, -, *, /, **, %
  • ~, &, |, ^, <<, >>
  • ==, !=, <, <=, >, >=
  • =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=

Przykłady

Odliczanie

a=10
while [[ $a -ge 0 ]]
do
echo $a
(( --a ))
sleep 0.2
done

Sprawdzanie czy liczba jest pierwsza

read n
k=2
while [[ $k -lt $n ]]
do
if (( n % k ))
then
(( ++k ))
else
echo Liczba $n nie jest pierwsza, $k jest nietrywialnym dzielnikiem.
break
fi
done
if [[ $k -eq $n ]]
then
echo Liczba $n jest pierwsza.
fi

Podstawianie

Wprowadzenie

Wspomniane do tej pory możliwości są bardzo podobne także w innych językach programowania niż powłoka. Tym co odróżnia powłokę od innych języków programowania jest, obok nacisku na interaktywność, możliwośc łatwego podstawiania wyników działania innych programów.

Wprowadzenie cd.

Powłoka nie jest językiem szczególnie szybkim i nie nadaje się do prowadzenia poważniejszych obliczeń (podobnie jak inne języki skryptowe). Błyszczy jednak jako „klej” zbierający i integrujący inne, niekoniecznie powiązane narzędzia. W tym kontekście bardzo przydatnym mechanizmem jest podstawianie wyników poleceń.

Podstawianie wyników poleceń

Kiedy powłoka natknie się na polecenie otoczone znakami ` lub $( i ), spróbuje wykonać znajdujące się wewnątrz znaki jako polecenie w podpowłoce i zastąpi cały ciąg wyjściem (stdout) z tego polecenia. Mechanizm ten jest powszechnie stosowany, a jeśli trzeba, pozwala łatwo dopisać potrzebną funkcjonalność w postaci osobnego programu (np. w C) i użyć go z poziomu powłoki.

Przykład

Później poznamy program make, który zarządza kompilacją (i nie tylko). Za pomocą jednego przełącznika można nakazać make używanie więcej niż jednego procesora. Aby dobrze wykorzystać sprzęt moglibyśmy nakazać używanie tylu procesorów ile jest dostępnych w systemie, poniższe polecenie to zrobi:

$ make -j `nproc`
$ make -j $(nproc)

Podstawienie arytmetyczne

Powłoka potrafi zamienić wyrażenie arytmetyczne na jego wartość. W tym celu należy otoczyć wyrażenie znakami $(( i )).

Przykład

Tradycyjnie do make przekazuje się liczbę o jeden większą niż liczba procesorów, moglibyśmy zrobić to tak, używając podstawienia arytmetycznego:

$ make -j $(( $(nproc) + 1 ))

Podstawianie zmiennych

Poznaliśmy już podstawianie wartości zmiennych przez $zmienna lub, w razie konieczności, ${zmienna}. Powłoka pozwala też na nieco bardziej złożone podstawienia wartości zmiennych.

Podstawienie zmiennych cd.

  • ${zmienna:-domyślnie} – wartość użyta gdy zmienna nie istnieje lub jest pusta
  • ${zmienna:start:długość} – wypisuje długość znaków zaczynając od znaku o numerze start, znaki numerujemy od 0
  • ${zmienna#szablon}, ${zmienna%szablon} – wypisuje wartość $zmiennej, pomijając prefiks(sufiks) pasujący do szablonu
  • ${zmienna/szablon/napis} – wypisuje wartość $zmiennej, zastępując fragment pasujący do szablonu przez napis

Cytowanie

Po co?

Powłoka używa spacji do oddzielania argumentów od siebie. Równocześnie parametry mogą również zawierać spacje. Operacja dzielenia na argumenty ma miejsce po podstawieniu wartości zmiennych. Oznacza to, że argumenty zawierające spacje (np. nazwy plików) mogą zostać poszatkowane. Aby temu zapobiec używamy cytowania.

mv $old $new

Pojedyncze cudzysłowy

Tekst otoczony pojedynczymi cudzysłowami (') będzie zachowany jako jeden argument dokładnie tak jak został wpisany. Między pojedynczymi cudzysłowami nie może pojawić się pojedynczy cudzysłów.

a='$a'
echo '$a='$a

Podwójne cudzysłowy

Tekst otoczony podwójnymi cudzysłowami (") będzie zachowany jako jeden argument, ale powłoka wykona dalej pewne operacje. W szczególności powłoka podstawi wartości zmiennych.

Aby wpisać znak, który ma specjalne znaczenie, trzeba go poprzedzić backslashem (\). W szczególności musielibyśmy napisać \$, \\ i \".

a=napis
echo "Zmienna a ma wartość $a"

Funkcje w bash-u

Definiowanie funkcji

Jak w każdym porządnym języku programowania, w powłoce można definiować funkcje. Składnia jest następująca

f() {
polecenia
}
function f() {
polecenia
}
function f {
polecenia
}

Funkcję wywołujemy podając tylko jej nazwę (bez nawiasów)

Zmienne lokalne i globalne

Domyślnie zmienne w powłoce są globalne, przeciwnie do większości innych języków programowania:

function f() { a=2; }
a=1
f
echo $a

Znaczy to, że wywołując funkcję ryzykujemy zamazanie istniejących zmiennych.

Zmienne lokalne

Aby uczynić zmienną lokalną wystarczy dodać słowo local:

function f() { local a=2; echo $a; }
a=1
f
echo $a

Argumenty funkcji

Do funkcji można przekazywać argumenty. Do argumentów mamy dostęp przez specjalne zmienne $0, $1, …, $9, ${10}, itd.

Normalne argumenty zaczynają się od $1. Argument $0 najczęściej nie jest interesujący.

Polecenie shift

Wywołanie wewnątrz funkcji/skryptu polecenia shift powoduje przesunięcie argumentów: pierwszy zostaje zastąpiony drugim, drugi – trzecim itd. Liczba argumentów pozycyjnych zmniejsza się o 1.

function f { echo $1; shift;
echo $1; shift;
echo $1; }
f 1 2 3

Zmienne specjalne

$#

Zmienna $# przechowuje liczbę argumentów pozycyjnych przekazanych do skryptu/funkcji.

function f { echo $#; }
f
f 1
f 1 2
f 1 2 3

$@

Zmienna $@ przechowuje wszystkie argumenty pozycyjne przekazane do skryptu/funkcji, poczynając od $1. Najczęściej używamy w cudzysłowach:

function f { for i in "$@"; do echo $i; done }
function g { for i in $@; do echo $i; done }
f "argument ze spacjami"
g "argument ze spacjami"

$?

Zmienna $? przechowuje kod wyjścia ostatniego polecenia.

true
echo $?
false
echo $?

Skrypty

Co to jest?

Skrypt powłoki to po prostu lista poleceń zapisanych w pliku, do wykonania przez powłokę. Skrypty tworzymy najczęściej aby automatyzować powtarzalne czynności

Skrypty powłoki mają zwykle rozszerzenie .sh.

Wykonanie w bieżącej powłoce

Aby wykonać w bieżącej powłoce polecenia pobrane z pliku skrypt.sh, używamy jednej z poniższych, równoważnych metod:

source skrypt.sh
. skrypt.sh

Tak wykonany skrypt może zmieniać wartości zmiennych bieżącej powłoki i ma dostęp do zmiennych, które nie zostały wyeksportowane.

Wykonanie w podpowłoce

Jeśli chcemy wykonać skrypt w podpowłoce, robimy:

bash skrypt.sh

Nie różni się to niczym od wykonania programu w jakimkolwiek innym języku skryptowym (Perl, Python, Ruby). Tak wykonywany skrypt nie ma dostępu do zmiennych, które nie zostały wyeksportowane.

shebang

Skryptom można też nadać uprawnienia do wykonania (uprawnienie x), tworząc z nich pliki wykonywalne. Aby system wiedział, że ten plik tekstowy jest skryptem powłoki (a nie np. programem w Perlu), w pierwszej linijce umieszczamy tzw. shebang, a następnie ścieżkę do programu powłoki:

#!/bin/bash

shebang cd.

Gdy system przy próbie uruchomienia pliku wykonywalnego zauważy, że dwa pierwsze znaki to #!, użyje następujących po nim znaków jako ścieżki do programu który będzie w stanie dany plik zrozumieć i wykonać. Aby to się powiodło, oprócz prawa wykonania musimy mieć też prawo odczytu skryptu (inaczej niż dla prawdziwych, binarnych plików wykonywalnych).

Argumenty skryptów

Skrypty przypominają programy i czasem chcemy przekazać do skryptu przełączniki i argumenty. Argumenty działają tak samo jak dla funkcji ($1, $2 itd.).

W przetwarzaniu przełączników (przypomnijmy, że są to szczególnie wyglądające argumenty pozycyjne) mogą być pomocne polecenie wbudowane getopts i program getopt.

Program make

Wprowadzenie

Program make powstał by automatyzować kompilację programów, oryginalnie w języku C. Język ten ma to do siebie, że programy mogą składać się z wielu plików, które można przetwarzać (kompilować) równolegle.

Program ten przydaje się, gdy chcemy w powtarzalny sposób otrzymywać pewne pliki z innych plików.

Możliwości

Użytkownik make deklaruje co może być zrobione, a narzędzie samo znajduje optymalną drogę osiągnięcia odpowiedniego celu.

W make deklarujemy tzw. reguły: pliki wynikowe wraz z plikami źródłowymi i poleceniami, które spowodują stworzenie plików wynikowych ze źródłowych. Następnie make sam sortuje reguły tak, aby polecenia mogły się powieść (pliki pośrednie tworzone przed ostatecznymi itp.), a wykona tylko te reguły, w których plik źródłowe się zmieniły.

Reguły

Reguły standardowo wpisujemy w pliku Makefile. Składnia pojedynczej reguły jest następująca:

cel: źródło1 źródło2
polecenia transformujące
pliki źródłowe w cel

Polecenia są poprzedzone znakiem tabulacji.

Co zrobi make?

W najprostszym przypadku, cel i źródła są plikami, gdzie źródła istnieją, a cel niekoniecznie. Jeśli wpiszemy teraz

$ make cel

to make sprawdzi czy czas modyfikacji któregokolwiek ze źrodeł są nowsze niż czas modyfikacji celu. Jeżeli tak, to zostaną wykonane odpowiednie polecenia. Program make nie sprawdza, czy polecenia rzeczywiście tworzą cel.

Bardziej skomplikowane sytuacje

Weźmy następujący plik makefile:

cel1: źródło1 źródło2
polecenie
cel2: cel1 źródło3
polecenie

Wykonanie $ make cel2 spowoduje, w razie konieczności także odświeżenie pliku cel1.

Specjalne reguły

Reguła może nie zawierać plików źródłowych, wtedy odpowiednie polecenia będą wykonane za każdym razem, gdy make będzie poproszony o stworzenie celu.

Reguła może też nie zawierać poleceń, jest to szczególnie przydatne przy celach sztucznych.

Sztuczne cele

Czasem cel nie jest nazwą pliku, tylko nazwą dla reguły. Takie cele nazywamy sztucznymi (phony). Dla przykładu:

clean:
rm -rf tmp

Wywołanie $ make clean spowoduje zawsze usunięcie pliku tmp.

Sztuczne cele cd.

Niektóre reguły istnieją tylko po to, by zgrupować cele innych reguł, np.:

all: program1 program2

Szablony reguł

Czasem wiele reguł ma powtarzalne listy poleceń, np. kompilacja plików *.c do plików *.o. Nie musimy wtedy powtarzać wielokrotnie tego samego, tylko stworzyć szablon reguły:

%.o : %.c
gcc -c -o $@ $<

Konwencje

Utarło się kilka konwencji, do których warto się stosować. Wtedy nasz plik Makefile będzie łatwiejszy w obsłudze przez innych.

  • Pierwszy cel/cel all powoduje skompilowanie/przebudowanie całego programu.
  • Cel install powoduje instalację programu w systemie.
  • Cel clean powoduje usunięcie plików tymczasowych

Następcy make

Inne narzędzia zarządzające kompilacją:

  • C/C++ – CMake, qmake, autotools, Meson, ninja
  • Java – Ant, Maven, Gradle
  • Javascript – Grunt
  • LaTeX – latexmk
  • CI – Jenkins, Travis CI