Operační systémy – materiály ke cvičení 2003 Ve cvičeních z Operačních systémů se studenti seznamují se základy práce v systému Unix/Linux (programování v shellu) a se základy komunikace procesů. S danými problémy se studenti seznamují prostřednictvím projektů, které během semestru řeší. Tento dokument má sloužit pro lepší orientaci a základní seznámení s prací v Unixu.
1. Základní pojmy a informace Unix - víceuživatelský (multiuser) a víceúlohový (multitask) OS xterm - emulátor znakového terminálu pro X terminál shell
- interpret příkazů (v DOSu COMMAND.COM) - běží ve znakovém terminálu (xterm) - začátek práce - přihlášení: login: xnovak00 Password: /* zadáte heslo */ - po přihlášení můžete zadávat příkazy shellu nebo spouštět programy - příkazový řádek: $ - standardní prompt příkaz odešleme stiskem Return (Enter, CR) příkazy a názvy souborů jsou na rozdíl od DOSu case sensitivní (rozlišují se malá a velká písmena: echo, ECHO, Echo jsou tedy různé soubory (programy) příkazy a systémové programy jsou malými písmeny (ls, cp, copy) funkci příkazů a systémových programů modifikujeme pomocí parametrů (přepínačů, argumentů) (ls -l /) příkaz [-přepínače] [argumenty] příkazový řádek můžeme opravovat klávesou BackSpace (Ctrl-h) historie příkazů: zpět Ctrl-p, dopředu Ctrl-n editace příkazové řádky: úplné smazání: Ctrl-u pohyb doleva: Ctrl-b pohyb doprav: Ctrl-f na konec: Ctrl-e na začátek: Ctrl-a mazání znaku: Ctrl-d - nový xterm: myší nebo xterm & - ukončení shellu: příkaz exit nebo Ctrl-D - více verzí shellu: sh, ksh, bash, csh - manuálové stránky: chceme-li získat informace o některém příkazu, použijeme příkaz man. Např. man ls pohyb pouze dolů - Space předčasné ukončení - q vyhledávání příkazu podle klíčového slova man -k concat
vyhledání výskytu slova v (již otevřené) manuálové stránce - man ls /file v manuálové stránce příkazu ls vyhledá výskyt slova file. Každý další výskyt - n. 1.1. Práce s myší 1. přesun okna, změna velikosti 2. zmenšení okna na ikonu 3. vyvolání menu (pravé tlačítko ve volném prostoru) 4. cut & paste označení textu: a) stisknout levé tlačítko a táhnout b) jedno slovo - dvakrát na něj kliknout c) celý řádek - třikrát kliknout umístění textu - prostřední tlačítko 1.2. Práce na vzdáleném počítači Pro práci na vzdáleném počítači si musíme k danému počítači přihlásit a spustit vzdáleně shell. Pro tuto operaci existuje např. nástroj ssh. ssh merlin.fit.vutbr.cz ssh implicitně předpokládá pro vzdálené přihlášení stejného uživatele, který spustí ssh. Změnu uživatele lze provést takto: ssh -l user merlin.fit.vutbr.cz nebo ssh
[email protected] Nástroj ssh (oproti nástroji telnet) používá zabezpečenou komunikaci. V současné době bývá na většině strojů služba telnet zakázána a lze použít pouze ssh. Kopírování souborů/adresářů Nástroj scp (používá ssh) - scp co kam Např. scp file
[email protected]:./data zkopíruje soubor file z lokálního počítače na počítač merlin.fit.vutbr.cz do domovského adresáře do podadresáře data scp -r
[email protected]:./data . zkopíruje rekurzívně adresář data (tj. celý adresář) z počítače merlin.fit.vutbr.cz na lokální počítač do pracovního adresáře
2. Systém souborů v OS Unix Soubory jsou organizovány v stromové hierarchii (/ - kořenový adresář (root), oddělovač adresářů). Př.: /home/vyuka/os1 Jako jméno souboru se chápou všechny znaky mimo /. Speciální znaky shellu (např. @, \, $, ;, !, &, *, |, ?, %, #, ~, (, ), …) se nedoporučují. Rozlišujeme relativní a absolutní jméno souboru. Příklad relativního jména souboru (seznam1.txt, ../seznam1) a absolutního jména souboru (/dokument/seznamy/seznam1.txt). Pracovní (aktuální) adresář je adresář, ve kterém se nacházíme (pwd). Domovský adresář - každý uživatel má svůj domovský adresář. Po přihlášení do systému se nachází v domovském adresáři. Může si v něm budovat vlastní adresářovou strukturu. Např.: /homes/xn/xnovak00 2.1. Příkazy pwd - výpis jména pracovního adresáře (Shell je nakonfigurován tak, že ukazuje jméno pracovního adresáře v promptu.) cd - změna pracovního adresáře (v DOSu také cd) relativní jména adresářů (./ ../) absolutní jména adresářů Př.: cd bez parametrů - pracovním adresářem bude domovský cd ~ to samé - znak tilda ~ zastupuje vždy domovský adresář cd přechod do předchozího (ne nadřazeného!!!) adresáře cd texty přechod do adresáře texty ls - výpis obsahu adresáře (v DOSu dir) užitečné modifikace: ls -l úplný výpis ls -a vypíše i skryté soubory (.profile) Pozn. místo ls -l -a můžeme psát ls -la ls -F označí adresáře (/) a spustitelné soubory (*) ls -R vypíše rekurzívně i obsahy podadresářů mkdir - vytvoření adresáře (v DOSu mkdir, md) rmdir - zrušení prázdného adresáře rm - smazání souborů (v DOSu del, erase) užitečné modifikace: rm -rf texty smaže adresář texty, který nemusí být prázdný (tj. smaže rekurzívně všechny podadresáře a soubory) !!! Vždy si dobře rozmyslete co mažete. V Unixu není žádná možnost obnovit smazané soubory (v DOSu undelete) !!!
cp - kopírování soubrů (v DOSu copy) cp zdroj cíl Pokud je jako cíl zadán adresář, vytvoří se v něm kopie zdrojového souboru. Jméno souboru zůstane zachováno. Pokud je jako cíl zadáno jméno již existujícího souboru, bude nenávratně přepsán. mv - přemístění (přejmenování) souboru (v DOSu rename) Vše je stejné jako u cp, ale původní soubor se smaže (opět nenávratně). ln - další jméno souboru (link) (v DOSu nic takového není) Jeden fyzický soubor na disku může mít více jmen. ln původní_jméno nové_jméno Při mazání souboru s více jmény (rm), zůstává soubor fyzicky na disku dokud existuje aspoň jedno jeho jméno. - symbolický link ln -s původní_jméno symbolický_link chová se jako "ukazatel na soubor" => Pokud smažeme soubor pod původním jménem, smaže se fyzicky z disku a symbolický link bude ukazovat do neznámého souboru.
2.2. Atributy souboru Atributy zjistíme příkazem ls -l 1. Přístupová práva (drwxrwxrwx) znak 1: b = blokové zařízení (např. disk, viz. /dev) c = znakové zařízení (např. terminál, viz. /dev) d = adresář l = symbolický link - = obyčejný soubor 1. trojice rwx - práva vlastníka 2. trojice rwx - práva skupiny 3. trojice rwx - práva všech ostatních uživatelů obyčejný soubor právo rwx-
čtení zápis (mazání) spustit jako příkaz
2. Počet jmen (linků) obyčejného souboru. Počet podadresářů + 2 (./ ../) u adresáře. 3. Vlastník 4. Skupina do níž vlastník patří
adresář vypisování obsahu vytváření a mazání souborů procházení do podadresářů
5. Velikost souboru adresáře: 96, 1024 6. Datum poslední modifikace 7. Jméno souboru 2.3. Příkazy pro změnu práv chown - změna vlastníka chown nový_vlastník jméno_souboru chmod - změna přístupových práv 1. chmod [ugoa]{ + | - }[rwx] file . . . 2. chmod 644 file -rw-r--r-Př.: chmod a+rwx file = chmod 777 file
3. Práce v shellu Shell je interpret příkazů, lze na něj pohlížet jako na programovací jazyk (if - then, for, case, proměnné etc.)
3.1. Základní příkazy (pro demonstraci vlastností shellu) cat - výpis obsahu souboru cat file1 výpis obsahu souboru file1 cat file1 file2 file3 výpis obsahu souborů cut - výběr určitých polí (sloupců) souboru cut -f2 file1 vypiš druhý sloupec implicitním oddělovačem je tab cut -f1,3 -d" " file1 vypiš první a třetí sloupec oddělovačem je mezera head - zobrazení prvních několika řádků souboru head -n 2 file1 první dva řádky souboru file1 tail - zobrazení několika posledních řádků souboru tail -n 2 file1 poslední dva řádky souboru file1 tail -n +2 file1 od druhého řádku do konce souboru more - výpis po obrazovkách more file1 sort - setřídění souboru sort file1 grep - hledání řetězce v souboru grep xnovak00 seznam vypíše řádky souboru seznam, které obsahují řetězec xnovak00 wc - (word count) počítá slabiky, slova nebo řádky souboru wc -l file1 spočítá řádky souboru file1 wc file1 spočítá všechno
Všechny tyto příkazy mohou fungovat i jako filtry. Tzn., že implicitně čtou ze standardního vstupu a zapisují na standardní výstup a lze je tedy řadit do kolon pomocí roury (viz dále). 3.2. Vstup, výstup, přesměrování Každý program otevírá aspoň 3 soubory: stdin (0), stdout (1) a stderr (2). Tyto soubory představují standardní vstup (klávesnice), standardní výstup a standardní chybový výstup (terminál). Tyto soubory lze přesměrovat na jiné soubory (např. std. výstup procesu na std. vstup jiného procesu, soubor na std. vstup procesu apod.) Přesměrování: stdin
< cat file1 = cat
stdout >, >> cat
file2 = file2 cat stderr 2>, 2>> rm neex_file 2>err Přklady: 1) 2)
cat file1 >file2 2>error Pokud file1 existuje, vypíše se do file2, pokud neexistuje, chybová hláška se zapíše do souboru error. cat file1 >file2 2>&1 Standardní i chybový výstup do sterného souboru file2.
Here document - umožňuje zapsat standardní vstup pro příkaz na příkazovou řádku (využívá se hlavně v dávkách) Př.: $ sort <<END > tento > text > bude > seřazen > podle > abecedy > END Vytvoření prázdného souboru: >file Zahození výstupu: program > /dev/null 3.3. Roury (kolony, pipe) Příklad: Zjistíme, zda je přihlášen uživatel xnovak00. 1) Bez použití roury (dočasný soubor). who >tmp grep xnovak00 tmp rm tmp
2)
Pomocí roury. who | grep xnovak00
tee - odbočka z kolony who | wc -l kolik lidí je přihlášených who | cut -f1 -d" " | tee WHO | wc -l kolik lidí je přihlášených a navíc bude jejich seznam v souboru WHO 3.4. Proměnné Promenné můžeme rozdělit na speciální a uživatelské, lokální a globální. Lokální proměnné se nepředávají do podřízených shellů, k docílení tohoto stavu je musíte exportovat (viz tabulka příkazů). Speciální (systémové): HOME PATH DISPLAY PS1 ...
absolutní cesta domovského adresáře seznam cest, které se prohledávájí při hledání umístění programu adresa a číslo displeje pro směrování požadavků na X-server formát promptu shellu a další ...
Příkazy pro práci s proměnnými env výpis speciálních proměnných set Výpis všech proměnných. unset Zruší proměnnou (nebude už definována) export exportuje proměnnou (stane se globální) a předává se do subshellů např. Export DISPLAY Proměnné definované uživatelem (měly by být malými psímeny). Nemají typ, zachází s nimi podle potřeby. a=123 Přiřazení hodnoty proměnné a (bez mezer). b=$a Přiřazení obsahu proměnné a do proměnné b (bez mezer). echo $b Zjištění obsahu proměnné b. Přiřazení výstupu programu do proměnné. c=`pwd` !!! zpětné apostrofy echo $c Proměnné se samy od sebe nepředávají do subshellů, musí se použít příkaz export. $ export DISPLAY
Další přístupy a manipulace s proměnnými: $proměnná, ${proměnná} obsah proměnné ${#proměnná} délka obsahu proměnné ${proměnná:-náhrada} Pokud proměnná není definována, nebo obsahuje prázdný řetězec, tak se vypíše náhrada. Jinak se vypíše obsah proměnné. ${proměnná:=náhrada} Pokud proměnná není definována, nebo obsahuje prázdný řetězec, tak se do ní přiřadí náhrada a vypíše se. Jinak se vypíše obsah proměnné. ${proměnná:+náhrada} Pokud proměnná je definována, tak se vypíše náhrada. Jinak se neděje nic. ${proměnná#reg.výraz} ${proměnná##reg.výraz} Pokud začátek obsahu proměnné odpovídá regulárnímu výrazu, tak se vypíše bez tohoto začátku, jinak se vypíše celý obsah proměnné. # je pro nejmenší začátek odpvídající reg. výrazu, ## je pro největší. ${proměnná%reg.výraz} ${proměnná%%reg.výraz} Jako #, ##, ale na konec. typeset Př.: malá
- nastavuje vlastnosti proměnných $ typeset -l a # proměnná bude převádět velká písmena na $ typeset +l a $ typeset
vlastnosti: -L[n] -R[n] -Z[n] -i -l -r -u -x
# zruší předchozí vlastnost # vypíše vlastnosti všech proměnných
- omezení velikosti obsahu na n znaků zleva - omezení velikosti obsahu na n znaků zprava - omezení velikosti obsahu na n znaků zprava, pokud je prvním znakem číslice, tak se zleva doplní nuly tak, aby celková délka obsahu byla n - s proměnnou se zachází jako s číslem, pokud se do ní přiřadí nějaký text, převede se na 0 - převod na malá písmena proměnná bude read only (konstanta) - převod na velká písmena - proměnná se bude exportovat do subshellů
3.5. Procesy ps - výpis procesů $ ps -u xnovak00 vypiš všechny procesy uživatele xnovak00 kill - zaslání signálu procesu $ kill -s KILL 14397 = $ kill -9 14397 zabije proces 14397. $ man 5 signal seznam signálů Práce na pozadí Přkazy, které trvají dlouho, nebo grafické aplikace spouštěné z příkazové řádky spouštíme na pozadí. Shell pak bude opět schopen přijímat příkazy. Výstupy programu spuštěného na pozadí je vhodné přesměrovat do souborů, aby se nevypisovaly na terminál. Znak & se aplikuje na celou rouru, protože roura se považuje za nový příkaz. Příklad: find / -name bin -print >vysl 2>/dev/null & Vyhledá všechny soubory (adresáře) bin v systému souborů. Výsledek hledání zapíše do souboru vysl a chybová hlášení se zahodí (/dev/null). Proces se spustí na pozadí (&), takže je shell schopen přijímat další příkazy. Na obrazovku se vypíše číslo jobu a PID posledního procesu v přpadné rouře. [1] 14071 Job na popředí se dá pozastavit Ctrl-Z. jobs - výpis jobů bg - přenese pozastavený job na pozadí bg %1 fg - přenese pozastavený job na popředí fg %1 kill - ukončí job kill %1 Spouštění více příkazů z jedné příkazové řádky ; - odděluje příkazy stejně jako Enter $ date; ls; who & - spustí všechny příkazy paralelně na pozadí $ xterm & xterm & xterm & && - druhý přkaz se spustí, pokud první příkaz skončil úspěšně || - druhý přkaz se spustí, pokud první příkaz skončil neúspěšně $ gcc -o myprog myprog.c && myprog Pokud skončí překlad programu myprog.c úspěšně, je spuštěn. $ gcc -o myprog myprog.c || vi myprog.c Pokud skončí neúspěšně, spusť editor. Přkazy se mohou sdružovat pomocí závorek. Př.: $ date; ls >file1 $ (date; ls) >file1 Př.: $ (sleep 5; date) & date
4. Progamování v shellu 4.1. Dávky (scénáře, skripty) # Toto je poznámka ve skriptu. Znak "#" musí být na začátku řádku, nebo bla bla bla # musí být oddělený od příkazů mezerou. Spouštění dávky: $ sh dávka $ . dávka $ dávka #! /bin/ksh
# spustí se kopie shellu # spouští tento shell; vhodné pro nastavování proměnných # musí být nastaveno právo x standardní začátek skriptu. Určujeme tím, který shell má dávku interpretovat v případě, že bude spuštěna jako příkaz (tj. nebude spuštěna jako parametr shellu).
Ladění scénářů: $ sh -x dávka $ sh -xv dávka 4.2. Zneplatnění zvláštního významu metaznaků '...' "..." \ Př.:
zneplatní vše interpretuje $, `...`, \ zneplatňuje následující znak $ echo $HOME * \\a `pwd` $ echo "$HOME * \\a `pwd`" $ echo '$HOME * \\a `pwd`'
4.3. Argumenty skriptu Název programu: $0 Poziční parametry: $1, $2, $3, ..., $9 Všechny arumenty: $* Počet argumentů: $# shift - posuv argumentů (použijeme, pokud je argumentů víc než 9) 4.4. Testování podmínek Každý program produkuje při svém ukončení a návratu do shellu exit status. $ echo $? zjistí exit status posledního příkazu Pokud je exit status 0, skončil program úspěšně, jinak došlo k nějaké chybě. Podmínka v shellu je pravdivá, pokud je 0.
Příkaz test - dvě formy zápisu 1) test -přepínač argument 2) [ -přepínač argument ] kolem závorek musí být na obou stranách mezery Příklady použití: test $a, [ $a ] test -f file1, [ -f file1 ] test -r file1, [ -r file1 ] test -d file1, [ -d file1 ] test -s file1, [ -s file1 ] test -h file1, [ -h file1 ] test -n str1, [ -n str1 ] test str1 = str2, [ str1 = str2 ] test n1 -eq n2, [ n1 -eq n2 ]
je proměnná a definovaná? existuje obyčejný soubor file1? existuje soubor file1 a je čitelný? podobně -w, -x existuje soubor file1 a je to adresář? existuje soubor file1 a je větší než 0 Byte? existuje soubor file1 a je to symbolický link? je délka řetězce str1 větš než 0? jsou řetězce str1 a str2 stejné? != ... různé jsou čísla n1 a n2 shodná?
další možnosti: -ne (různá), -gt (větší než), -ge (větší nebo rovno), ... viz. man test !!! Pozor na rozdíl mezi porovnáváním řetězců (=) a čísel (-eq). Na podmínky se mohou aplikovat logické operátory AND (-a) a OR (-o). Př.: [ -f file1 -a $prom = "ahoj" ] 4.5. Programové konstrukce Podmíněný příkaz if if podmínka1 then příkaz1 elif podmínka2 then příkaz2 ... else příkazn fi Př. Test exit statusu programu (rm). $ if rm file1 2>/dev/null > then echo file1 byl smazán > else echo soubor file1 neexistuje > fi
Př. $ if [ "" != `who | grep xnovak00` ] > then echo xnovak00 je přihlášen > else echo xnovak00 není přihlášen > fi
Cyklus while a until while podmínka do příkazy done until podmínka do příkazy done Výběr varianty case slovo in vzor1) příkaz1;; vzor2) příkaz2;; ... esac Ve vzorech lze použít regulární vyrazy (*, ?, [a-z], [1,2,3]). Cyklus for for proměnná in seznam do příkazy done
$ for i in 1 2 3 4 > do echo $i > done
Př.: Všem uživatelům systému zašli zprávu # zbytečně složité - pouze pro ilustraci for #stačilo by: mail `cat /etc/passwd | cut -d: -f1` <message $ for i in `cat /etc/passwd | cut -d: -f1` > do > mail $i <message > done Poznámka exit - předčasné ukončení scénáře read - načtení hodnoty z klávesnice do proměnné Jestliže se interpretuje výstup ze zpětných apostrofů (for i in `ls`), považuje se znak konec řádku za oddělovač slov.
Příklad na for, if a case Maže soubory v aktuálním adresáři. Před každým smazáním se pro jistotu zeptá. #! /bin/ksh for i in * # for i in `ls` … přes všechny soubory do if [ -f $i ] # je-li to obyčejný soubor then echo Smazat $i? read tak_co case $tak_co in y|Y|a|A|yes|YES|ano|ANO) rm $i;; n|N|no|NO|ne|NE) echo nemažu $i;; q|Q|quit|QUIT) echo konec; exit;; esac fi done Pro předčasné opuštění těla cyklu lze použít příkazy break a continue (stejně jako v C.) Další příkazy od - výpis obsahu (binárnho souboru) which - zjistí adresář, ve kterém se nachází spustitelný soubor file - zjistí typ souboru du - vypíše velikost adresáře df - zjistí volné místo na discích cmp, diff - porovnávání souborů
5. Filtry, regulární výrazy Nyní si popíšeme regulární výrazy, jejich vlastnosti a filtry, které stojí nad těmito výrazy (používají je k specifikaci řádků či podřetězců, se kterými se má manipulovat). Budeme se zabývat filtry sed, grep, awk a částečně perl. 5.1. Regulární výrazy Regulární výraz je univerzální pomocník při práci s textem. Používá jej celá řada programů v Unixu. Umožňují prohledávat soubory (grep, egrep), editovat je (sed, vi), analyzovat a vypočítávát zajímavé údaje (awk) či nabízejí plnohodnotný programovací jazyk, kde si můžete dělat, co vás napadne (Perl, Tk). Nejjednodušším regulárním výrazem je obyčejné písmeno - třeba r. Když se v textu hledá řetězec, který by tomuto regulárnímu výrazu vyhověl, hledá se jednoduše písmeno "r". Implicitně se (jak bývá v Unixu zvykem) rozlišují malá a velká písmena. Ve většině nástrojů můžete tuto vlastnost vypnout. Regulární výrazy lze řetězit. Použijete-li regulární výraz root, představuje vlastně zřetězení čtyř elementárních jednopísmenných regulárních výrazů. Výsledkem je chování, které byste očekávali v textu se bude hledat slovo "root".
Základní prvky tvořící regulární výraz si uvedeme v následující tabulce: výraz ===== znak . [znaky] [a-zA-Z] [abc] [^znaky] [.^az-] \x
vyhovuje ======== odpovídající znak libovolný znak, kromě znaku konec řádku jeden z uvedených znaků jeden ze znaků a-z nebo A-Z jeden ze znaků a b c libovolný znak kromě uvedených znaky "^" a "-" mají speciální význam jen v daném kontextu jeden ze znaků "." "^" "a" "z" "-" vyřadí/zapne speciální význam znaku x
* A* [a-z]*
opakování výrazu (nepovinné) libovolné opakování znaku "A", nebo se vůbec nevyskytuje libovolné opakování znaku jednoho ze znaků "a-z", nebo se vůbec nevyskytuje
Srovnávání výrazů Pojmem srovnávání (matching) se označuje proces, při kterém program hledá, zda předložený řetězec odpovídá regulárnímu výrazu. Zároveň stanoví, které části řetězce odpovídají jednotlivým částem regulárního výrazu. Princip srovnávání je následující: začíná se od začátku řetězce. Každému prvku reg. Výrazu se snaží přiřadit co nejdelší posloupnost znaků z řetězce a pak postupuje dále srovnáváním dalších částí reg. výrazu. Pokud něco nevyjde, vrací se zpět a zkusí přiřazený podřetězec o jeden znak zkrátit. Pokud se nepodaří najít shodu ani po zkrácení na minimum, posune se na další znak zkoumaného řetězce a pokračuje znovu ve vyhledávání. Uvažujme následující 2 reg. výrazy nad řetězcem text: regulárnímu výrazu <.*> bude přiřazen celý řetězec text | -----------------------------| < .* > regulárnímu výrazu <[^>]*> bude přiřazen podřetězec text |-------------------| < [^>]* >
5.2. Filtry sed, grep Proudový editor sed spouštění: a) sed 'seznam_příkazů' soubory b) sed 'seznam_příkazů' c) sed -f cmd_file
# čte stdin # příkazy čte ze souboru cmd_file
Parametry sedu: -i nerozlišovat malá a velká písmena -w vybírat jen řádky, na nichž vzoru vyhovuje celé slovo -v negovat výsledek (vypisovat řádky, které nevyhovují vzoru) Příkazy odpovídají příkazům editoru ed. - bez uvedení rozsahu platí na každý řádek - nelze použít relativní definice rozsahu (-3,+5) - jeden průchod - vzory: ., x*, .*, [abc], [^abc], [a-z], [^a-z], ^, $ - rozsah lze zadat dvojicí vzorů: $ ... | sed '/BEGIN/,/^$/s/text1/text2/g' | ... # s - substituce nahradí všechny výskyty text1 za text2 na řádcích od prvního výskytu slova BEGIN do prvního následujícího prázdného řádku (^$) - implicitní vlastností sedu je výpis řádku, takže $ sed 10p soubor.txt výpíše celý soubor soubor.txt a 10. řádek tam bude dvakrát. - tuto vlastnost lze potlačit přepínačem -n: $ sed -n 10p soubor.txt vypíše jenom 10. řádek. - negace rozsahu - příkaz se provede pouze na řádcích, které neodpovídají vzoru: $ sed '10,20!s/ahoj/čau/g' soubor.txt provede substituce na všech řádcích vyjma od 10. do 20. další příkazy: q - ukončí sed: $ sed 10q soubor.txt vypíše prvních 10 řádků a pak skončí. y - náhrady jednotlivých znaků $ sed 'y/abcd/ABCD/' soubor.txt nahradí písmena a,b,c,d odpovídajícími velkými písmeny. d - vymaže všechny řádky mezi řádky vyhovující intervalu daném reg. výrazy $ sed '/vzor1/,/vzor2/d' soubor.txt Příkaz (nástroj) tr - převod znaků - není vázaný tak jako sed na jednotlivé řádky, tj. nepředpokládá textový soubor. Mohou se nahrazovat (mazat) libovolné znaky. - pracuje jako čístý filtr => neumí si otevřít soubor. Př.: $ tr [a-z] [A-Z] <soubor.txt převede malá písmena v souboru soubor.txt na velká $ tr -d 0123456789 <soubor.txt smaže všechny číslice
Filtry grep, fgrep, egrep spouštění: a) grep 'vzor' soubory b) grep 'vzor' # čte stdin c) grep -f cmd_file # vzor čte ze souboru cmd_file - vybírá řádky, na kterých se vyskutují řetězce odpovídající vzoru - přepínač -y - ignoruje velká/malá - vzory grepu (jako sed): ., x*, .*, [abc], [^abc], [a-z], [^a-z], ^, $ fgrep - stejně jako grep, navíc lze hledat více vzorů současně. V tomto případě ale musí být vzory zapsány v souboru cmd_file - každý vzor na samostatném řádku. egrep - regulární výrazy mohou navíc obsahovat: x+ ve významu xx* x? ve významu žádný nebo jeden výskyt znaku x () sdužování reg. výrazů | alternativy (jako fgrep, ale nemusí to být v souboru - egrep je ale v tomto případé výrazně pomalejší 5.3. Programovatelný filtr awk Spuštění: awk 'program' soubory aplikuje program na soubory awk 'program' aplikuje program na datový tok (filtr) awk -f cmd_file program je uložen v souboru cmd_file Tvar programu: vzor { akce } vzor { akce } ... Aplikuje akci na řádky, na kterých se vyskytuje řetězec odpovídající vzoru. Vzory odpovídají vzorům sedu (/reg_výr/; /reg_výr1/,/reg_výr2/), regulární výrazy odpovídají regulárním výrazům egrepu (., .*, a*, a+, a?, [abc], [^abc], [a-z], [^a-z], |(bez mezer), ^, $). Navíc lze jednotlivé vzory kombinovat pomocí logických operátorátorů !, &&, || a určovat prioritu pomocí závorek (). Př.: !(/ahoj/ || /čau/) { akce } # akci proveď na řádcích, kde se nevyskytuje ani slovo ahoj, ani slovo čau Vzory lze též pomocí znku ~ aplikovat pouze na jednotlivá pole. Př.: $2~/ahoj/ { akce }
# provede akci na těch řadcích, které maj ve druhém poli obsažen řetězech ahoj
Negace: ! na rozdíl od sedu je před vzorem !($2~/ahoj/) nebo $2!~/ahoj/ Pokud se neuvede vzor, je akce aplikována na všechny řádky. Speciální vzory: BEGIN, END (bez lomítek) - akce se provede před zpracováním prvního řádku, resp. posledního řádu. Místo vzoru lze použít i jiné testy, například s proměnnými: Př.: NR >= 3 && NR <= 6 { print $0 } # vytiskne třetí až šestý řádek awk rozděluje text na záznamy a záznamy na pole. Implicitním záznamem je jeden řádek, implicitním polem je slovo (posloupnost znaků mezi mezerami a tabelátory). Celý záznam je uložen v $0, jednotlivá pole v $1, $2, ..., $10, ... Nejčastější akcí je tisk: { print $0 } { print $3 $1 } { print $3, $1 } { print $3 "*" $1 }
# vytiskne celý záznam (řádek) # vytiskne třetí a první pole (slovo) # vytiskne třetí a první pole (slovo); navíc k oddělování polí použije OFS (viz. dále) # vytiskne třetí a první pole (slovo) oddělené *
Pokud funkce print nestačí, lze použít funkci printf známou z C. { printf("%s*%s\n", $3, $1) } # viz. předchozí příklad Zápis argumentů fukce printf je zde ale trochu volnější než v C, např.: { printf $3"*"$1"\n" } Lze použít speciální znaky (i ve funkci print) \a - bell, \b - BS, \n – LF, \r - CR, \t - TAB, \nnn znak zadaný oktalově. Pokud se neuvede akce, vypisuje se implicitně celý záznam (řádek). Vzor však musí být v tomto případě zadán. Proměnné: 1) vestavěné např.: FS
- vstupní oddělovač polí (implicitně "[ \t]+") FS=":" FS="\t+" OFS - výstupní oddělovač polí (implicitně " ") RS - vstupní oddělovač záznamů (implicitně "\n") ORS - výstupní oddělovač záznamů (implicitně "\n") NF - počet polí právě zpracovávaného záznamu NR - číslo právě zpracovávaného záznamu FILENAME - jméno zpracovávaného souboru OFMT - výstupní formát čísel (implicitně "%.6g")
2) uživatelské přiřazení: a=12.89, a=$1, a=length($2), a="ahoj" Proměnné nemají typ, zachází se s nimi podle potřeby. Numerické operace jsou jako v C: přiřazení: =, +=, -=, /=, %=, *=, ^= relace: ==, !=, <, >, <=, >= aritmetické operace: +, -, *, /, %, ^(mocnina), ++, -programové konstrukce - jako v C: if, ?:, while, do, for, break, continue !!!podmínky zase jako v C!!! (0 - false, !=0 - true) pole: for (i=0; i<10; i++) a[i] = 2*i Př.: tisk záznamů (řádků) v obráceném pořadí { line[NR] = $0 } END { for(i=NR; i>0; i--) print line[i] } k indexování pole lze použít libovolný řetězec: name[karel] = "novák" Př.: Jan 200 Karel 100 Jan 300 ... chceme součet pro každé jméno: { sum[$1] += $2 } END { for (name in sum) print name, sum[name] } (jiný for - přes všechny indexy pole) exit - skočí na vzor END Funkce: řetězcové: length(string) substr(string, pos, len) index(string, substr) match(string, reg_expr) split(string, a, fs) sub(for, repl, in) gsub(for, repl, in) aritmetické: sin, cos, exp, log, sqrt
# délka řetězce # vrátí podřetězec délky len od pozice pos # vrátí pozici substr v řetězci string # hledá rag. výraz v řetězci a vrátí jeho pozici # rozdělí řetězec string na jednotlivá pole, která uloží do a[1], a[2], ... Oddělovačem polí je regulární výraz fs # nahradí první výskyt retězce vyhovujícímu regulárnímu výrazu for z řetězec repl v retězci in # ... všechny výskyty ...
6. Komunikace procesů 6.1. Identifikátory procesů Každý proces má unikátní nezáporné číslo -- identifikátor. Protože je tento identifikátor unikátní, často se využívá pro zaručení unikátnosti např. dočasných souborů. Kromě běžných procesů existují také procesy speciální. Proces 0 je např. plánovač, známý též jako swapper. S tímto procesem nekoresponduje žádný program na disku. Proces 1 je init -- tento proces je inicializován při startovací (bootstrap) proceduře. Tomuto procesu odpovídal v dřívějších verzích unixu soubor /etc/init, v novějších verzích /sbin/init. Procesy, které se spouštějí při inicializaci unixu, mají odpovídající soubory v /etc/rc* a jsou rozděleny podle jednotlivých stavů systému. Proces init nikdy neumírá. Je to normální proces (není uvnitř jádra jako např. swapper). Na některých implementacích unixu je přítomen ještě pagedaemon, který zajišťuje správu virtuální paměti. Tento proces číslo 2 je opět v jádře, jako swapper. Pro identifikátor procesu (PID) existuje početná skupina funkcí, např.: #include <sys/types.h> #include pid_t getpid (void); Vrací: ID volajícího procesu pid_t getppid (void); Vrací: ID rodiče volajícího procesu 6.2. Funkce fork Jedinou cestou, jak v unixu vytvořit nový proces, je použití funkce fork. #include <sys/types.h> #include pid_t fork (void); Vrací: v potomkovi 0, v rodiči PID potomka, -1 při chybě Nový proces vyprodukovaný fork se nazývá potomek (child). Funkce se volá jednou, ale vrací dvě hodnoty. Pomocí návratové hodnoty program určí svou totožnost. Vrátí-li fork -1, došlo k chybě -- pravděpodobně došlo k překročení počtu souběžně běžících procesů. Vrátí-li 0, je zřejmé, že kód, testující tuto hodnotu, je potomkem. Rodičovskému (parent) procesu vrátí fork identifikační číslo potomka. Rodič musí disponovat PID potomků pro synchronizaci činnosti. Rodič i potomek provádějí stejný kód po volání funkce fork. Je čistě věcí programu, jak rozdvojení procesů ošetří. 6.3. Funkce wait a waitpid V případě, že proces skončí normálně, zasílá jádro rodiči procesu signál SIGCHLD. Protože je zánik potomka asynchronní událostí, je i zaslání signálu asynchronní. Implicitní reakcí je ignorování tohoto signálu. Proces ale může na signál čekat pomocí funkce wait. Zpracování programu po volání wait může mít tři stavy: blokován -- potomci stále běží okamžitý návrat z wait -- potomek doběhl okamžitý návrat z wait -- žádný potomek neexistuje Volání wait má následující syntaxi:
#include <sys/types.h> #include <sys/wait.h> pid_t wait (int *statloc); pid_t waitpid (pid_t pid, int *statloc, int options); Obě vrací: PID když OK, 0, -1 při chybě Hlavní rozdíly mezi funkcemi: wait blokuje volajícího, dokud potomek nedoběhne, zatímco waitpid poskytuje nástroje zabraňující blokování waitpid nečeká na dokončení jen prvního potomka -- pomocí podmínek lze řídit, na který proces má čekat Argument statloc určuje status ukončení potomka. Pokud nás tento nezajímá, lze nastavit na NULL. pid u funkce waitpid může nabývat těchto hodnot: -1. Čeká na dokončení libovolného potomka. V tomto stavu je ekvivalentní funkci wait >0. Čeká na dokončení potomka s PID daným pid. ==0. Čeká na dokončení libovolného potomka, jehož skupinové ID je shodné s ID skupiny volajícího procesu. <-1. Čeká na dokončení potomka, jehož skupinové ID je shodné s absolutní hodnotou pid.
6.4. Signály Signály jsou vlastně softwarová přerušení. Každý netriviální program má co do činění se signály. Každý signál má své jméno. Jméno každého signálu začíná na SIG. Jména těchto signálů jsou definována v souboru <signal.h>. Žádný z těchto signálů nemá hodnotu 0, POSIX.1 totiž definuje signál null. Pro více informací viz man signal a man 7 signal. Signály může generovat terminál po stisku speciálních kláves (přerušení např. pomocí Control-C generuje signál SIGINT). Významy kláves, jako vysílačů signálů, jdou přemapovat. Hardwarová přerušení generují signály (dělení nulou, ...). Funkce kill(2) Příkaz kill(1) Uživatelské programy mohou generovat signály (např. SIGPIPE, SIGALRM). Signály jsou klasickou asynchronní záležitostí. Reakce na ně mohou být následující: Ignorování signálu. Dva signály: SIGKILL a SIGSTOP nejdou ignorovat. Zachycení signálu a zpracování vlastní funkcí. Ponechání standardního zpracování signálu.
Nejjednodušším prostředníkem k signálům je funkce signal. #include <signal.h> void (*signal (int signo, void (*func)(int)))(int); Vrací: předchozí obsluhu signálu
Za argument signo lze dosadit název signálu. Argument func může nabývat následujících hodnot: Konstantu SIG_IGN -- ignorování signálu. Konstantu SIG_DFL -- nastavení standardní obsluhy. Adresu funkce pro zpracování signálu. Příklad: #include <signal.h> static void sig_usr(int);
/* one handler for both signals */
int main(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR2"); for ( ; ; ) pause(); } static void sig_usr(int signo) /* argument is signal number */ { if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGUSR2) printf("received SIGUSR2\n"); else err_dump("received signal %d\n", signo); return; }
Pro práci se signály lze využít následující příkazi (pro bližší informace viz manuálové stránky): kill raise alarm pause
6.5. Roury Roury (pipes) jsou nejstarším komunikačním prostředkem v unixu. Jsou podporovány všemi typy unixů za těchto omezujících podmínek: Jedná se o poloviční duplex. Data putují jen jedním směrem. Mohou být použity pouze v případě, že procesy mají společného předchůdce. Roura se vytvoří vyvoláním funkce pipe. #include int pipe (int filedes[2]); Vrací: 0 když OK, -1 při chybě Funkce vrátí dva deskriptory pomocí argumentu filedes. Deskriptor filedes[0] je otevřen pro čtení a filedes[1] pro zápis. Když je jeden konec roury zavřený, děje se následující: Jestliže čteme z roury, jejíž vstup byl uzavřen, read vrátí 0. Jestliže zapisujeme do roury, kterou nikdo nečte, unix generuje SIGPIPE. Příklad: int main(void) { int pid_t char
n, fd[2]; pid; line[MAXLINE];
if (pipe(fd) < 0) err_sys("pipe error"); if ( (pid = fork()) < 0) err_sys("fork error"); else if (pid > 0) { /* parent */ close(fd[0]); write(fd[1], "hello world\n", 12); } else { /* child */ close(fd[1]); n = read(fd[0], line, MAXLINE); write(STDOUT_FILENO, line, n); } exit(0); }
7. Příklady, úkoly 1. Vytvořte skript "mymail", který bude zpracovávat informace o mailech. Skript vytvořte v shellu bez pomoci vyšších nástrojů (awk, perl, apod.) a jakéhokoli kompilovaného jazyka. Skript bude tvořen jediným souborem. Skript čte informace o mailech, uložených v souboru "inbox", a tiskne na standardní výstup informace o mailech ve formátu html. Soubor "inbox" je uložen v adresáři "mail", který je uložen v pracovním adresáři skriptu. 2. Vytvořte skript "users", který vygeneruje seznam aktuálně přihlášených uživatelů na aktuální server. Seznam bude obsahovat informace o uživateli, jeho poštovní schránce a počet přihlášení uživatele na server. 3. Vytvořte program checkword na kontrolu slov v anglickém textu podle slovníku. Program může tvořit více skriptů (např. v shellu a v perlu), základní skript se bude jmenovat checkword a tento se bude spouštět z příkazové řádky. Jednotlivá slova jsou v slovníku uložena na zvláštních řádcích. Při průchodu si dejte pozor na interpunkční a jiné znaky (,.;?!"'\()<>[]) a na velikost písmen (program při hledání slov nesmí rozlišovat malá a velká písmena)! Syntax použití skriptu a jeho chování je následující: checkword [-n | -v] [-d {dictionary}] {file|directory} {dictionary} ... soubor se slovníkem (nepovinný, pokud není zadán je implicitním souborem soubor s jménem dictionary.txt) {file|dictionary} ... kontrolovaný soubor nebo adresář. 4. Vytvořte skript "users", který zpracovává informace o uživatelích. Skript vytvořte v shellu bez pomoci vyšších nástrojů (awk, perl, apod.) a jakéhokoli kompilovaného jazyka. Skript bude tvořen jediným souborem. Použití dočasných souborů je zakázáno. Skript má neomezený počet vstupních parametrů. Každý parametr se chápe jako počáteční znak (znaky) loginu. Skript zpracovává soubor "/etc/passwd" a tiskne na standardní výstup počet uživatelů, jejichž login začíná na uvedený znak (znaky). 5. Implementujte v jazyku C komunikaci dvou procesů pomocí roury. Program po spuštění vytvoří synovský proces. Otec čte znaky ze standardního vstupu a posílá je prostřednictvím roury synovskému procesu. Ten přijatá data tiskne na standardní výstup. Po ukončení přenosu dat otec čeká na ukončení syna a poté se sám ukončí. 6. Implementujte v jazyku C komunikaci procesů pomocí signálů. Program periodicky vypisuje na std. Výstup svoje PID každé 2 sekundy. Ošetřuje signály SIGINT (číslo 2) a SIGQUIT (číslo 3). 7. Napište program (v jazyku C), který otestuje synchronizaci rodič-potomek. Proces vytvoří soubor a zapíše do něj číslo 0. Potom zavolá fork a rodič a potomek střídavě inkrementují počítadlo v souboru. Pokaždé, když je počítadlo zvětšeno, tiskněte, který proces to provedl (rodič nebo potomek).