Skriptování v Bournově shellu/Prostředí

◄ Skriptování v Bournově shellu/Spouštění příkazů Prostředí Skriptování v Bournově shellu/Expanze proměnných ►

Žádný program neběží zcela izolovaně, ani Bournův Shell. Každý program je spouštěn ve svém prostředí, tedy systému zdrojů, které ovlivňují jeho běh a vstup a výstup a které může program sám pro sebe upravovat. Tomuto prostředí se budeme věnovat v této kapitole. Nejvýraznějším prvkem, který má shell k předávání dat, jsou takzvané proměnné prostředí.

Prostředí

editovat

Termín prostředí (anglicky environment) se v diskusích při programování skriptů objevuje velmi často. Označuje se jím kontext, v kterém skript běží, a obvykle se jím míní zejména množina „proměnných prostředí“ (o těch později). Nicméně slovo prostředí může mít také širší význam, kdy kromě samotných proměnných znamená i jiné skutečnosti, které ovlivňují běh programu. Pro tento širší význam budeme dále používat označení běhové prostředí příkazu.

Běhové prostředí příkazu

editovat

Každý program, který je v systému spuštěn, ať už uživatelem z shellu nebo nějakým jiným programem, pracuje v kontextu zvaném běhové prostředí příkazu (krátce BPP).

Běhové prostředí příkazu obsahuje například důležité informace týkající se vstupu a výstupu dat (takzvaného standardního vstupu, standardního výstupu a chybového výstupu). Dále obsahuje řadu důležitých proměnných.

Úplný seznam zdrojů obsažených v shellovém BPP je následující:

  • Soubory otevřené rodičovským procesem, který shell spustil. Přístup k těmto souborům proces zdědí a to včetně souborů zpřístupněných pomocí přesměrování
  • Aktuální pracovní adresář, tedy „současný“ adresář shellu
  • Přednastavené nastavení práv pro nově vytvářené soubory
  • Aktivní zpracování signálů
  • Proměnné a parametry zadané shellu při jeho spouštění
  • Shellové funkce zděděné z rodičovského shellu.
  • Přepínače shellu nastavené příkazy set nebo shopts nebo pomocí parametrů příkazové řádky při jeho spuštění
  • Aliasy (pokud je shell podporuje)
  • Procesové ID shellu a některých procesů spuštěných jeho rodičovským procesem

Kdykoliv shell provádí příkaz spouštějící nový proces, pak tento nový proces pouští v jeho vlastním BPP. Potomek dostává do svého vznikajícího BPP okopírovanou část rodičovského BPP, ale nikoliv celé BPP. Dědí se:

  • Otevřené soubory.
  • Pracovní adresář
  • Přednastavení práv pro zakládané soubory
  • Všechny proměnné a funkce označené k exportu potomkům
  • Nastavené obsluhy signálů

Příkaz set

editovat

Příkaz 'set' umožňuje nastavit nebo odnastavit řadu konfiguračních možnost, které jsou součástí BPP. Pro nastavení přepínače se předá příkazu set parametr '-' následovaný jedním nebo více jmény přepínačů. Odnastavení je možné podobným způsobem, ale coby úvodní parametr se použije '+'. Vůbec nejobvyklejší je ale puštění příkazu bez parametrů, kdy set vypíše jména všech definovaných proměnných a funkcí v prostředí. Přestože je nastavování přepínačů poměrně vzácná záležitost, některé zajímavější zmiňme v následujícím neúplném seznamu:

±a
Je-li nastaven, jsou všechny nově vytvořené nebo změněné proměnné nastaveny pro export
±f
Je-li nastaven, nebere shell ohled na metaznaky
±n
Je-li nastaven, jsou příkazy pouze čteny, nikoliv prováděny
±v
Je-li nastaven, pak jsou příkazy při čtení vypisovány
±x
Je-li nastaven, pak jsou příkazy při vykonávání vypisovány

Prostředí a proměnné prostředí

editovat

Součástí BPP je prostředí v užším slova významu: Sbírka dvojic jméno=hodnota, kterým se říká proměnné prostředí. Technicky vzato mezi proměnné prostředí patří i funkce, ale ty budeme probírat ve zvláštní kapitole a zde se jim věnovat nebudeme.

Každá proměnná prostředí je vlastně malým pojmenovaným úložištěm, kam si může uživatel nebo skript uschovat informace dle libosti. Tato úložiště se nazývají proměnné, protože jejich obsah se může měnit. K přístupu k obsahu proměnné stačí znát její jméno. Samotný shell zpřístupňuje uživateli některé informace skrz vybrané proměnné prostředí. Skripty mohou proměnné číst a rozhodovat se na základě jejich hodnot.

Syntaxe vytváření (respektive dosazování do) proměnných má následující podobu (nepřítomnost mezer je důležitá!):

promenna=hodnota

Je více způsobů, jak využít obsah proměnné (a bude o nich řeč v kapitole Expanze proměnných), nyní zmiňme jen ten základní: Když před jméno proměnné napíšeme znak '$', pak shell nahradí jméno proměnné její hodnotou.

$ PROMENNA=Ahoj
$ echo $PROMENNA
Ahoj

Zmiňme ještě pár důležitých proměnných shellu, tedy takových, jejichž hodnotu shell nastavuje nebo naopak její nastavení ovlivňuje jeho činnost:

IFS
Tato proměnná obsahuje seznam znaků, které shell považuje za bílé
PATH
Tato proměnná je interpretována jako seznam adresářů (oddělených dvojtečkou). Kdykoliv se uživatel pokusí spustit nějaký příkaz. aniž by uvedl plnou cestu k programu, pokusí se shell hledat program toho jména v tomto seznamu adresářů a to podle pořadí, v kterém jsou v seznamu uvedeny.
PS1
Tato speciální proměnná obsahuje formát promptu v interaktivním shellu
PWD
Obsahem této proměnné je vždy cesta do aktuálního adresáře

Proměnné jsou ve své podstatě velmi obecné, mohou být využívány kterýmkoli programem, v jehož prostředí jsou, a mohou obsahovat jakékoliv řetězce znaků, třeba číslice. Co vlastně znaky znamenají, je pak z velké části dáno tím, jak je program bude interpretovat, a i zde bude mít velkou míru svobody. Na příklad může do proměnné uložit jméno programu a pak využitím proměnné tento program zavolat (v tomto případě program ls sloužící k výpisu adresáře):

PRIKAZ=ls
$PRIKAZ

Různé druhy proměnných prostředí

editovat

Zatím jsme věc trochu zjednodušili a mluvili jen o proměnných, které mají jméno. Ve skutečnosti jsou ovšem i zvláštní proměnné, které jsou například označeny číslem. Proberme nyní rozdíly mezi různými typy proměnných.

Pojmenované proměnné

editovat

O pojmenovaných proměnných už jsem mluvili, jsou to prostě schránky na data. Mají jméno a je-li před něj připojen znak '$', pak shell vypíše jejich obsah. Jsou vytvořeny automaticky v okamžiku, kdy je jim přiřazena hodnota pomocí rovnítka a nabývají hodnoty na jeho pravé straně, například:

PROMENNA=Ahoj

Hodnotu proměnné můžeme kdykoliv změnit:

PROMENNA=Sbohem

A při dosazování do proměnné se nemusíme omezit na pevně daný řetězec, ale můžeme dosadit například obsah jiné proměnné:

CESTA=$PATH

Dokonce můžeme do proměnné dosadit složený výstup několika příkazů:

PS1="`whoami`@`hostname -s ` `pwd` \$ "

V tomto příkladu používáme tři různé příkazy: whoami, hostname a pwd, navrch přidáme znak '$', trochu bílých znaků a jiného formátování, a výsledkem je nová podoba stavového hlášení příkazového řádku.

V prostředí se obvykle vyskytuje mnoho pojmenovaných proměnných – zkuste se podívat příkazem 'set'.

Číslované proměnné

editovat

Většina proměnných prostředí jsou sice běžné pojmenované proměnné, ale kromě nich shell nabízí některé speciální proměnné, kterým také nastavuje hodnoty.

Nejčastěji se používají právě číslované proměnné, proměnné parametrů. Každý příkaz, který se v shellu spouští (ať už v interaktivním režimu nebo ve skriptu), může dostat na příkazové řádce parametry. To se dělá tak, že se zkrátka za příkaz ony parametry napíší:

$ ls adresář1 adresář2 adresář3  

To lze udělat u každého příkazu, včetně shellových skriptů napsaných uživatelem. Číslované proměnné jsou tu právě od toho, aby se skript dostal k hodnotám svých parametrů: První parametr lze získat jako proměnnou $1, druhý parametr jako proměnnou $2 a tak dále až do $9. Zvláštní parametr $0 navíc vrací jméno příkazu. Můžeme tedy mít například následující skript SParametry.sh:

#!/bin/sh

echo $0
echo $1
echo $2

a ten zavolat s parametry:

$ SParametry.sh Ahoj svete
SParametry.sh
Ahoj 
svete

Do proměnné $1 tedy shell dosadil první parametr „Ahoj“ a do proměnné $2 druhý parametr „Svete“. Co kdybychom tento skript zavolali s více parametry?

$ SParametry.sh Ahoj svete budiz pozdraven
SParametry.sh
Ahoj 
svete

Nebude to problém — nevadí, že jsme proměnné $3 a $4 nepoužili, přestože jsou mají definovánu hodnotu. Výstup tedy bude stejný, jako předtím. Ale co v opačném případě, kdy se budeme snažit využít víc parametrů, než kolik bylo skriptu zadáno?

$ SParametry.sh Ahoj
SParametry.sh
Ahoj

Vlastně ani toto nebude problém. Tam, kde by se měl vypisovat obsah proměnné $2, bude vypsán prázdný řádek, protože tato proměnná nic neobsahuje. Samozřejmě, kdyby byly dvě proměnné pro běh skriptu podstatné, bylo by na místě si zkontrolovat, že jsou obě neprázdné (jak, to se dozvíme později)

Co kdybychom chtěli, aby dvě slova oddělená mezerou byla brána jako jeden parametr? To lze zařídit různými způsoby, prozatím se spokojíme s variantou, kdy uzavřeme parametr jednoduchými uvozovkami:

$ SParametry.sh 'Ahoj svete' 'budiz pozdraven'
SParametry.sh
Ahoj svete
budiz pozdraven
Posunování
editovat

Prvním devět parametrů máme tedy přímo přístupných přes číslované proměnné. Co když se chceme dostat k dalším, co když má náš skript třeba třináct parametrů? Pak nám pomůže příkaz shift. Ten způsobí posunutí hodnot číslovaných parametrů. Pod $1 je pak přístupná hodnota druhého parametru, pod $2 hodnota třetího a tak dále. Další volání shiftu zpřístupní jako $1 třetí parametr, jako $2 čtvrtý parametr a tak dále. Pokud chceme provést větší posun, tak také rovnou můžeme zavolat shift s volitelným parametrem v podobě přirozeného čísla, například shift 3 provede trojnásobný posun.

Nabízí se otázka, jak se pak dostaneme opět k hodnotě prvního parametru. Nijak. Pokud ji budeme ještě potřebovat, musíme si ji (a případně další parametry, ke kterým posunem ztratíme přístup) uložit do jiné proměnné.

Dále je vhodné si zapamatovat, že hodnota $0 se nemění. A že nelze žádat o větší posun, než kolik je parametrů, to by se žádný posun neprovedl.

V kapitole věnované řízení běhu si ukážeme, jak můžeme v cyklu projít všechny parametry, aniž bychom předem věděli, kolik jsme jich dostali.

Další zvláštní proměnné

editovat

Kromě číslovaných proměnných nabízí Bournův shell ještě řadu dalších zvláštních proměnných. Některé z nich asi použijete zřídka, ale je dobré o nich vědět, až přijde jejich příležitost.

$#
Počet „zbývajících“ parametrů skriptu na příkazové řádce (použitím příkazu shift se hodnota mění)
$-
Aktuálně nastavené přepínače shellu (viz příkaz set
$?
Návratová hodnota posledního provedeného příkazu (0 úspěch, nenulová hodnota neúspěch)
$$
PID současného procesu
$!
PID posledního procesu puštěného na pozadí
$*
Všechny parametry z příkazové řádky. Pokud je uzavřeno v uvozovkách, pak jsou parametry vypsány dohromady zavřeny v uvozovkách ("$*" = "$1 $2 $3 … ")
$@
Všechny parametry z příkazové řádky. Pokud je uzavřeno v uvozovkách, pak jsou parametry vypsány každý zvlášť v uvozovkách ("$*" = "$1" "$2" "$3" … ")

Exportování proměnných podprocesu

editovat

Jak už bylo dříve zmíněno, Unix je víceuživatelský víceprocesový operační systém a Bournův shel je navržen, aby tyto jeho vlastnosti podporoval. Snadno tedy můžete přímo z běžícího shellu spustit další proces, nebo dokonce několik procesů následně prováděných souběžně. Můžeme si například z shellu spustit další shell příkazem sh:

$ sh

Také jsme dříve mluvili o tom, jak důležité je běhové prostředí programu a jak důležité jsou proměnné prostředí. Je také důležité, aby se nastavení proměnných nešířila z procesu do procesu nekontrolovaně — jistě nechcete, aby váš skript psal žlutými písmeny na modrém pozadí jen proto, protože byl spuštěn z Midnight Commanderu. Na druhou stranu, je příjemné, když jsou některé proměnné potomkem současného procesu přebrány a tento tak nezačíná se zcela prázdným prostředím.

Aby se tedy skrze své prostředí vzájemně neovlivňovaly, jsou jednotlivá prostředí oddělena. To znamená, že když se spustí nový proces, vznikne pro něj jeho vlastní prostředí. Ovšem bylo by poměrně nepohodlné, kdyby nové prostředí bylo zcela prázdné, kdyby například nebyla nastavena proměnná PATH určující cestu k spustitelným programům, nebo kdyby zde nebyla nakonfigurována podoba promptu. Na druhou stranu je určitě spousta proměnným, u kterých naopak nechceme, aby je potomek zdědil, ať už třeba jen proto, aby jeho prostředí neobsahovalo zbytečné proměnné a nebyl v něm tedy nepořádek.

Bourne shell proto nabízí nástroj, jak řídit, které proměnné budou předány potomkům do jejich kopií prostředí a které ne. Mluví se o exportování proměnných a příkaz, kterým se říká, že kopie dané proměnné se má předat potomkům, se jmenuje export. Například proměnná $PATH je nastavena tak, aby byla exportována, což osvětluje následující příklad:

$ VAR=hodnota
$ echo Cesta:$PATH
Cesta:/usr/local/bin:/usr/bin:/bin
$ echo Promenna:$VAR
Promenna:hodnota
$ sh
$ echo Cesta:$PATH
Cesta:/usr/local/bin:/usr/bin:/bin
$ echo Promenna:$VAR
Promenna:

Vidíme, že i potomek stále zná hodnotu proměnné $PATH z rodičovského procesu, ale nezná hodnotu proměnné $VAR. Pokud ji nejdřív exportujeme, pak ji ovšem potomek znát bude, například

$ VAR=hodnota
$ echo Promenna:$VAR
Promenna:hodnota
$ sh		# spuštění potomka
$ echo Promenna:$VAR
Promenna:
$ exit		# opuštění potomka
$ export VAR 	# jsme zpět v rodičovském procesu a označíme proměnnou VAR k exportu
$ sh		# opět spustíme potomka
$ echo Promenna:$VAR
Promenna:hodnota

Vidíme tedy, že exportování funguje. Některé novější shelly, například Korn Shell a Bash, mají nad exportováním jemnější kontrolu. Například je možné označit proměnnou k exportování zároveň s její definicí. Jiným běžným rozšířením je možnost proměnnou zase odexportovat. Tyto možnosti nicméně Bournův shell nenabízí.

Přichází přirozené otázky: Kde se berou proměnné, které už jsou nastavené, když spustíte shell? A lze nějak měnit jejich podobu? Co kdybych chtěl mít ve všech shellech nějakou svoji proměnnou?

Částečnou odpovědí je informace, že zde existuje skript, který se pouští při každém nalogování. Jmenuje se .profile a je uložený v domovském adresáři, jedná se tedy o soubor $HOME/.profile (proměnná HOME obsahuje cestu k domovskému adresáři uživatele). Je to shellový skript jako každý jiný, kromě nastavování proměnných může například spouštět při nalogování nějaké programy.

Většina Un*xů má nějakou svou přednastavenou podobu souboru .profile, která je uživateli zkopírována do jeho domovského adresáře při vytváření uživatelského účtu. Obvykle vypadá v tomto duchu:

#!/bin/sh

f [ -f /etc/profile ]; then
 . /etc/profile
fi

PS1= "`whoami`@`hostname -s` `pwd` \$ "
export PS1

Ve světle předchozího textu se může zdát tato podoba překvapivá: přímo v souboru .profile se nastavuje vlastně jediná proměnná, PS1. Při bližším zkoumání vidíme, že je (v případě, že existuje) pouštěn soubor /etc/profile. A to je právě to místo, kde se nastavuje většina přednastavených proměnných. Je to tak proto, že většina přednastavených („systémových“) proměnných je pro všechny uživatele stejná, nejlepším řešením v duchu principu modularity je tedy mít jejich nastavení na jednom místě. Při dalším zkoumání, odkud se bere která proměnná, tedy nezbývá než se podívat do souboru /etc/profile. K jeho změnám je ovšem oprávněn jen správce systému (obvykle uživatel jménem root).

Naopak ve svém souboru .profile si můžete dělat změny, jaké potřebujete. Je ovšem dobrý nápad nevyhazovat z něj volání systémového skriptu /etc/profile.

Více úloh a jejich kontrola

editovat

Pro dnešní rychlé počítače, jejichž procesory umí rychle přepínat mezi procesy, nebo které mají dokonce více jader, je současný běh více procesů běžnou záležitostí. Ale i u starších počítačů se současný běh více procesů uplatnil například v situaci, kdy beztak jeden z procesů jen čekal na vstup z periférií.

Aby bylo možné využívat běh více úloh, musí to podporovat prostředí operačního systému. Musí tu být možnost spustit program a nechat ho běžet na pozadí, vrátit uživateli kontrolu, aby mohl spustit zároveň další program. Je také dobré, aby si program na pozadí mohl vyžádat pozornost, třeba když potřebuje nějaký vstup od uživatele, například zadání hesla.

Příkladem programu, který běží na pozadí, může být stahovač souborů. Dostane seznam souborů, které má stáhnout z Internetu, a pak nás zajímá až v okamžiku, kdy se mu podaří soubory stáhnout. Rozhodně nechceme čekat s používání počítače, až stahování doběhne.

Jiným příkladem je přehrávač hudby. Dostane seznam skladeb a dokud nechceme něco změnit, tak není důvod s ním interagovat.

Nyní si popíšeme podrobněji prostředky shellu pro správu úloh.

Pár termínů

editovat

Než se začneme probírat tím, jak shell s úlohami pracuje, ujasněme si terminologii. To nám umožní vyhnout se ve výkladu nedorozuměním a také to usnadní porozumění popisům z jiných zdrojů, neboť budeme vycházet z terminologie ustálené.

Základním termínem je úloha, tedy program běžící v rámci vlastního procesu. Můžeme mluvit také o procesu nebo o instanci programu, v anglických textech je základním termínem job, ale lze potkat také task nebo process. My se budeme držet termínu úloha a když budeme rozebírat schopnosti shellu používat a ovlivňovat běh úloh, budeme mluvit o správě úloh.

Úloha
Proces realizující instanci programu.
Správa úloh
Řízení spouštění, zastavování a pokračování úloh.

Další termíny:

ID úlohy
Jednoznačné označení úlohy, obvykle v podobě přirozeného čísla. Používáno některými programy a příkazy k práci s úlohou.
ID procesu (PID)
Jednoznačné označení procesu, obvykle v podobě přirozeného čísla. Používáno některými programy a příkazy k práci s procesem. Nemusí se rovnat ID úlohy.
Úloha na popředí
Úloha, která má přístup k terminálu (tedy může vypisovat výstup na monitor a přijímat vstup z klávesnice)
Úloha na pozadí
Úloha, která nemá přístup k terminálu (tedy nemůže vypisovat výstup na monitor nebo přijímat vstup z klávesnice)
Zastavení (uspání) úlohy
Zastaví provádění úlohy, ale na rozdíl od ukončení je možné posléze nechat úlohu pokračovat.
Ukončení
Odstraní program z paměti a zruší úlohu, která ho prováděla.

Řízení úloh z shellu

editovat

Když na klasickém terminálu spustíte úlohu z shellu obvyklým způsobem, převezme tato úloha správu monitoru i klávesnice: cokoliv je vyťukáno na klávesnici, jde přímo úloze, stejně tak i každý příkaz myší. Nic kromě dané úlohy nemá možnost psát na monitor. Proto se o takové úloze říká, že běží na popředí, protože je na ni dobře vidět, ale ona sama „zakrývá výhled“ na úlohy v pozadí.

Pokud je nám puštění jediné úlohy málo, například když chceme spustit nějakou dlouhodobou úlohu, která je neinteraktivní (řekněme zálohování pevného disku), můžeme takovou úlohu spustit na pozadí a tím si zachovat možnost dále na daném terminálu pracovat.

Tím možnosti ovšem nekončí. Co když píšeme dlouhý dokument v textovém editoru a chceme si odskočit k přečtení elektronické pošty? Nešlo by dočasně odsunout úlohu editoru na pozadí a spustit poštovního klienta, toho pak ukončit a zase se vrátit k editování?

Přesně tím se budeme zabývat, když se budeme učit přepínat v shellu mezi úlohami: jak spustit úlohu na pozadí, jak pozastavit běžící program a jak spustit pozastavený program, ať už na pozadí, nebo na popředí.

Povolení řízení úloh

editovat

Abychom se mohli do řízení úloh spustit, potřebujeme dvě základní věci:

  • operační systém, který podporuje řízení úloh a
  • shell, který podporuje řízení úloh a má je zapnuté

Un*xové systémy podporují běh více úloh i řízení úloh, protože podpora běhu více úloh je provází už z dob, kdy byl Unix teprve vymýšlen.

Asi čekáte, že řízení úloh samozřejmě podporuje i Bournův shell. Možná dokonce předpokládáte, že se bude jednat o záležitost do značné míry unifikovanou mezi různými shelly, která všude funguje stejně. A asi překvapí, že se pletete. Původní Bournův shell nenabízel žádnou podporu pro řízení úloh, byl to shell pro běh jedné úlohy. Existovala ovšem rozšířená verze Bournova shellu zvaná jsh (a v tom j asi tušíte anglické job znamenající úlohu), která podporovala řízení úloh. Tuto rozšířenou verzi původního Bournova shellu si můžete spustit i dnes příkazem:

jsh -i

V takto spuštěném shellu budeme fungovat po zbytek této kapitoly. Ještě zmiňme, že modernější shelly mají podporu řízení běhů obvykle už ve své základní verzi a to v podobě standardizované standardem POSIX 1003 a v interaktivním režimu ji mají obvykle zapnutou. Naopak v neinteraktivním režimu, kdy je často žádoucí co největší kompatibilita s Bournovým shellem, ji mohou mít vypnutou.

Puštění úlohy na popředí a co s ní

editovat

O puštění na popředí jsme si vlastně povídali už docela dost: zkrátka se na příkazovou řádku napíše příkaz, respektive jméno spustitelného programu, odešle se entrem a program neboli úloha od té chvíle běží na popředí.

Dokonce i o spouštění na pozadí jsme už mluvili: dělá se to přidáním ampersandu na konec řádky.

ls * > /dev/null &

Na terminál se vypíše něco ve stylu

[1] 4808

a pak je vráceno řízení. Ona jednička v hranatých závorkách je ID úlohy zmiňované dříve, a to obvykle podstatně větší následující číslo je ID procesu. Daná čísla lze použít k práci s danou úlohou, respektive s daným procesem, a patřičné nástroje s nimi umí pracovat. Až úloha doběhne, na terminál se vypíše něco ve stylu:

[1]+	Done ls * > /dev/null &

Nyní tedy ke zmíněným nástrojům. Prvním z nich je příkaz fg. Ten vezme úlohu běžící na pozadí a přesune ji na popředí. Pusťme na pozadí úlohu, která zabere nějaký čas:

while [ $POCET -lt 200000 ]; do echo $POCET >> vystup.txt; POCET=$(expr $POCET + 1); done &

Zatím jsme se nezabývali řízením běhu programu, nicméně si můžeme prozradit, že tato úloha vypíše do souboru vystup.txt prvních 200 000 přirozených čísel. A to jí zabere nějaký čas. A protože jsme ji spustili na pozadí, vypíše se něco ve stylu

[1] 11246

a dostaneme řízení zpět, přičemž víme, že ID úlohy je 1 a ID procesu je 11246. Na popředí ji dostaneme příkazem

fg %1

a že již běží na popředí poznáme z toho, že se nám řízení nevrátí. Řízení zpět dostaneme klávesovou zkratkou CTRL-Z, což nám vypíše:

[1]+  Stopped                 while [ $POCET -lt 200000 ]; do
   echo $POCET >> vystup.txt; POCET=$(expr $POCET + 1);
done

a vrátí řízení.

Možná jste si všimli slovíčka Stopped. To tam není napsáno jen tak. Zkuste si párkrát příkazem cat vypsat obsah souboru vystup.txt. Zjistíte, že se nemění. Naše úloha totiž teď neběží na pozadí, naše úloha teď neběží vůbec, ale je pozastavena.

Pokud je úloha pozastavena, je možné ji zase odbrzdit a to jak na pozadí, tak na popředí. Na popředí k tomu slouží již předvedený příkaz fg zatímco pro rozběhnutí na pozadí slouží příkaz 'bg':

bg %1

Vypíše se

[1]+  while [ $POCET -lt 200000 ]; do
   echo $POCET >> vystup.txt; POCET=$(expr $POCET + 1);
done

a vrátí se nám řízení.

Můžeme nějak pozastavit úlohu běžící na pozadí? Tedy jinak než přesunutím na popředí a zmáčknutím CTRL-Z? Ano, jde to, ale není na to žádný jednoúčelový nástroj. Musíme použít signály, o kterých si budeme podrobně povídat ve zvláštní kapitole. Zde jen uveďme, že pro pozastavení úlohy na pozadí lze použít příkaz:

kill -SIGSTOP IDulohy

A dodejme, že důvod k takovému použití se najde jen zřídka. Smyslem puštění na pozadí přece bylo, aby úloha běžela.

Přehled nástrojů řízení úloh a stavů úloh

editovat

Už jsme zmínili, že standard POSIX 1003.1 standardizoval pro řízení úloh řadu nástrojů, které byly v jsh shellu a jeho potomcích. Některé z nich jsme už zmínili, nyní si probereme úplný seznam:

bg
přesune úlohu na pozadí
fg
přesune úlohu na popředí
jobs
vypíše aktivní úlohy
kill
pošle úloze nebo procesu signál
CTRL+C
ukončí proces (totéž jako poslat pomocí kill signál SIGTERM)
CTRL+Z
pozastaví proces na popředí
wait
vyčká na ukončení procesů na pozadí

Všechny tyto příkazy mohou dostat jako parametr ID úlohy. Musí být zadáno se znakem procent na začátku a může mít různé formáty:

%n
kde číslo n je ID úlohy
%s
úloha, jejíž příkazová řádka začínala řetězcem s
%?s
úloha, jejíž příkazová řádka obsahuje řetězec s
%%
naposled spravovaná úloha
%+
naposled spravovaná úloha
%-
předchozí úloha

O bg, fg a CTRL+Z jsme si už povídali a o killu si podrobně řekneme v jiné kapitole. Zbývá probrat job a wait.

Wait slouží k synchronizaci: Při použití bez parametrů pozastaví volající proces, dokud všechny úlohy na pozadí neskončí. Pokud dostane na příkazové řádce seznam úloh, pak pozastaví proces až do ukončení těchto úloh. To se hodí v případech, kdy je za účelem efektivnějšího využití systémových prostředků spuštěno několik úloh na pozadí a nelze dál pokračovat, dokud neskončí. Taková situace nastává spíš až u skriptování komplikovanějších projektů. Je tedy možné, že wait hned tak nepotkáte.

Naopak příkaz jobs si možná zvyknete používat pravidelně. Kromě ID úlohy může mít následující parametry:

-l
vypíše i ID úloh
-n
vypíše jen ty úlohy, jejichž stav se od minulého výpisu změnil
-p
vypíše ID procesu jenom u vůdčího procesu úlohy
-r
vypíše jen běžící úlohy
-s
vypíše jen pozastavené procesy

Nástroj jobs tedy poskytuje informace o aktivních úlohách (tím se rozumí něco jiného než běžící úlohy). Jak už jméno napovídá, nástroj se týká pouze úloh, nikoliv procesů. Protože úlohy jsou záležitost konkrétního shellu, nástroj neposkytne žádné informace o procesech spuštěných z jiných shellů. Poskytuje informace právě o těch úlohách, které můžete z tohoto shellu řídit – například můžete získat jejich ID, abyste je mohli použít jako parametr pro jiné příkazy. Ukažme si to na příkladu.

CITAC=0
while [ $CITAC -lt 200000 ]; echo $CITAC >>vystup.txt; CITAC=`expr $CITAC + 1`; done &
# vypíše něco ve stylu
# [1] 26859
CITAC2=0
while [ $CITAC2 -lt 200000 ]; echo $CITAC2 >>vystup2.txt; CITAC2=`expr $CITAC2 + 1`; done &
# vypíše něco ve stylu
# [2] 31331
jobs
# vypíše něco ve stylu
#[1]- Running                 while [ $CITAC -lt 200000 ]; do
#    echo $CITAC >> vystup.txt; CITAC=`expr $CITAC + 1`;
#done &
#[2]+  Running                 while [ $CITAC2 -lt 200000 ]; do
#    echo $CITAC2 >> vystup2.txt; CITAC2=`expr $CITAC2 + 1`;
#done &

Tedy vidíme, že příkaz jobs vypisuje úlohy a k nim i jejich čísla, včetně informace, která úloha má zaměření (ta je označená znaménkem „+“) a která ho měla předtím (ta je označená znaménkem „-“). Kromě toho je oznámen i stav úlohy (v našem případě dvakrát frází „Running“, běží). Rozeberme si jednotlivé stavy:

Running – běží
To je ten stav, kdy úloha dělá svou práci. Je to typický stav úloh, které jsou na popředí.
Sleeping - spí
Když úloha potřebuje vstup, který není k disposici, je zbytečné, aby spotřebovávala výpočetní zdroje. V takovém případě se dočasně ukládá ke spánku a probudí ji až příchod požadovaných dat.
Stopped - zastavený
Tento stav znamená, že úloha byla zastavena operačním systém. K tomu obvykle dojde současně s tím, kdy ji uživatel pošle na pozadí zmáčknutím Ctrl-Z, nebo zasláním signálu SIGSTOP. Podobně jako spící úloha neběží, ale k rozběhnutí místo dat potřebuje pokyn od uživatele nebo operačního systému, například signálem SIGCONT.
Zombie
tyto úlohy se objeví v okamžiku, kdy jejich předek skončí dříve, než mu mohly předat návratovou hodnotu. Časem je odstraní proces init.

Jiné nástroje pro řízení úloh

editovat

Kromě již popsaných standardních nástrojů, které najdete v opravdu každé instalaci Bournova shellu, stojí za zmínku i několik nástrojů, které jsou tak rozšířené, že je pravděpodobně také bude mít k disposici.

stop & suspend

editovat

Příkaz stop se vyskytuje zejména v shellech vycházejících z rodiny System V. Slouží k zastavení úlohy na pozadí stejným způsobem, jakým by to udělal signál SIGSTOP. Jako parametr obvykle přijímá ID úlohy. Pokud není k disposici, zastane jeho práci příkaz kill, kterým je možné poslat dané úloze signál SIGSTOP.

Příkaz suspend je rozšířen nejméně ve dvou významech. V jednom případě funguje stejně jako stop, tedy přijímá jako jediný parametr ID úlohy a tu zastaví.

V druhém případě zastavuje aktuální shell s výjimkou shellů přihlašovacích, u kterých to ovšem lze vynutit přidáním parametru -f.

Nástroj ps (process snapshot) není součástí shellů, ale na běžných unixech je poměrně rozšířený. Slouží k podrobnějšímu vypsání běžících procesů.

PID    TTY     STAT  TIME    COMMAND
32094  tty5    R     3:37:21 /bin/sh
37759  tty5    S     0:00:00 /bin/ps

Typický výstup programu ps zahrnuje ID procesu, ID terminálu, ke kterému je připojen, spotřebovaný čas procesoru a příkaz z řádky, kterým byl puštěn. Další možnou informací je stav procesu udaný jedním písmenem, například Running, Sleeping, sTopped a Zombie. Velkým problémem ohledně ps je nedostatek standardizace, takže různé implementace nabízí různé množiny přepínačů. Běžné jsou:

-a
vypiš všechny procesy kromě vůdčích procesů skupiny
-d
vypiš všechny procesy kromě vůdčích procesů sezení
-e
vypiš všechny procesy bez ohledu na uživatelské účty
-g seznam
vypiš jen ty procesy, pro které je ID vůdčího procesu skupiny v seznamu
-l
podrobný výpis
-p seznam
vypiš všechny procesy, jejichž ID jsou zahrnuty v seznamu
-s seznam
vypiš všechny procesy, u nichž je v seznamu zahrnut jejich vůdčí proces sezení
-t seznam
vypiš všechny procesy běžící na terminálech zahrnutých v seznamu
-u seznam
vypiš všechny procesy, které patří uživatelským účtům ze seznamu
◄ Skriptování v Bournově shellu/Spouštění příkazů Prostředí Skriptování v Bournově shellu/Expanze proměnných ►