Index 🏠 || 🔝 Nahoru


Publikováno:

Aktualizováno

Série »Linux shell«

Kategorie:

PSKáčko

Tagy:

Každý program, který na Unixu/Linuxu spustíme dává vědět o svém zdárném nebo nezdárném konci svou návratovou hodnotou. Pokud je návratová hodnota 0 program skončil úspěšně. Pokud je návratová hodnota od nuly různá, skončil neúspěšně. Návratovou hodnotu naposledy vykonaného příkazu zjistíme pomocí proměnné $? do které se vždy uloží.

1
2
3
4
5
6
$ ls data
$ echo $?
0
$ ls nejakaBlbost
$ echo $?
2

Nenechte se poplést tím, že úspěch je značen nulou. Čím větší je návratová hodnota, tím větší je neúspěch.

Program true má návratovou hodnotu vždy 0. Program false má návratovou hodnotu vždy 1. Návratová hodnota příkazu se používá k řízení běhu, když píšete skript. Všechny if-else, while atd. na tom stojí.

Oddělování příkazů

Pokud chceme umístit na jeden řádek několik příkazů za sebe používá se jako oddělovač jednotlivých příkazů znak ;.

1
$ sleep 5 ; ls data

Konstrukce && a || slouží k podmíněnému vykonání příkazů.

&&AND

1
$ mkdir texty && cp *.txt texty

Příkaz mkdir se vykoná vždy. Ale příkaz cp bude vykonán pouze pokud příkaz mkdir skončil úspěšně. Příkaz && má význam logického součinu: Vykoná se první a zároveň druhý příkaz. Pokud se nevykoná první, nevykoná se ani druhý.

||OR

1
$ cd dddata || pwd

Příkaz pwd bude vykonán, pokud příkaz cd skončí neúspěšně. || má význam logického součtu: Vykoná se první nebo druhý příkaz; když se nevykoná první, musí se vykonat ten druhý.

Vyhledání spustitelného souboru

Shell vyhledává program pro spuštění v cestách, které jsou v proměnné $PATH.

1
2
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games

Je-li zadán například příkaz ls, shell hledá postupně spustitelný soubor ls v adresáři /usr/local/bin, potom v /usr/bin, potom v /bin atd., dokud takto pojmenovaný spustitelný soubor nenajde. Pokud ho najde, spustí ho. Pokud ho nenajde, vypíše chybové hlášení.

Pokud chci například spustit svůj vlastní program uložený v aktuálním adresáři

1
2
$ mujprogram.py
bash: mujprogram.py: příkaz nenalezen

shell program nespustí, protože aktuální pracovní adresář není v proměnné $PATH. Je proto nutné shellu říct, kde program je. To udělám tak, že k němu napíšu plnou cestu. Plnou cestu můžu napsat relativně

1
$ ./mujprogram.py

nebo absolutně:

1
$ /home/uzivatel/bin/mujprogram.py

Pokud chceme vyhledat, kde se program nachází, slouží k tomu příkaz which nebo type. Já mám raději type, protože odhalí i aliasy. Přepínač -a oba programy donutí, aby vypsaly nejen první, ale všechny nalezené možnosti.

1
2
3
$ type -a ls
ls je alias na 'ls -F --color=auto'
ls je /bin/ls

Vidíme, že spuštěním příkazu ls se nespouští /bin/ls jak by se podle dříve řečeného zdálo, ale tzv. alias. Rozdíl můžete porovnat pokud zadáte „holý” příkaz ls a když zadáte plnou cestu /bin/ls.

Expanze speciálních konstrukcí

Před spuštěním příkazu provádí shell nad příkazovou řádkou několik transformací — expanzí. Jejich pořadí má každý shell trochu jiné. Výsledek každé expanze vstupuje do té následující a vypadá to přibližně takto:

Na tomto místě rozhodně není uveden seznam všech konstrukcí, které lze použít. Vybírám jen ty nejpoužívanější/nejznámější.


Aliasy

Alias definuje zkratku pro jiný (dlouhý) příkaz. Typicky se vytváří aliasy:

1
2
$ alias ls='ls -F --color=auto'
$ alias ll='ls -l'

Alias „žije” pouze po dobu běhu příkazového interpretu a po jeho ukončení je zapomenut. Proto se aliasy ukládají do souboru, který se automaticky spouští při startu shellu. Například ~/.bashrc nebo ~/.zshrc (písmena rc na konci znamenají run commands).

V Zsh lze alias obejít zápisem =příkaz: Zsh expanduje =ls na plnou cestu k programu (např. /bin/ls), čímž se alias přeskočí:

Konstrukce =příkaz je ekvivalentní k zápisu plné cesty, ale nemusíte ji znát. Funguje pouze v Zsh, nikoliv v Bash.

Process substitution

Process substitution umožňuje použít výstup příkazu jako soubor. Shell spustí příkaz a jeho výstup zpřístupní přes dočasný file descriptor /dev/fd/N. Hodí se tam, kde program očekává soubor, ale my máme jen výstup příkazu (stdout).

1
$ diff <(ls adresar1) <(ls adresar2)

Program diff očekává dva soubory — process substitution mu je „dodá” bez nutnosti vytvářet dočasné soubory ručně.

Konstrukce Popis
<(prikaz) výstup příkazu jako vstupní soubor
>(prikaz) co je do „souboru” zapsáno, jde na vstup příkazu

Funguje v bash a zsh, nikoliv v klasickém sh.

Expanze proměnných

Proměnnou vytvoříme pomocí znaku =

1
$ PROMENA=data

Pozor, kolem = nesmí být mezery! Zápis PROMENA = data nefunguje – shell by se pokusil spustit program PROMENA s parametry = a data.

Obsah proměnné je možné vybrat pomocí znaku $:

1
$ ls $PROMENA

Takto vytvořená proměnná existuje pouze v aktuálním shellu. Pokud chceme, aby se předávala i programům, které z shellu spustíme, je nutné ji exportovat do podřízených shellů:

1
$ export PROMENA=data

Vnitřní proměnné shellu

… některé proměnné existují i bez toho, abychom je museli vytvářet:

Proměnná Význam
$$ PID shellu
$! PID posledního procesu spuštěného na pozadí
$? návratová hodnota posledního dokončeného procesu

Rozšířená expanze proměnných

Základní tvar $PROMENA lze zapsat i jako ${PROMENA} — složené závorky jsou nutné tam, kde je potřeba odlišit název proměnné od okolního textu:

1
$ echo "Soubor: ${PROMENA}.txt"

se to příliš nepoužívá, ale je to skvělé pokud píšete skripty:

Zápis Chování
${VAR} hodnota proměnné VAR
${VAR:-default} hodnota VAR, nebo default pokud VAR není nastavena
${VAR:=default} hodnota VAR, nebo nastav VAR na default a vrať ji
${VAR:?chyba} hodnota VAR, nebo vypiš chyba a ukonči shell
${#VAR} délka hodnoty VAR
${VAR#vzor} odstraň nejkratší shodu vzor ze začátku
${VAR##vzor} odstraň nejdelší shodu vzor ze začátku
${VAR%vzor} odstraň nejkratší shodu vzor z konce
${VAR%%vzor} odstraň nejdelší shodu vzor z konce
${VAR/old/new} nahraď první výskyt old za new
${VAR//old/new} nahraď všechny výskyty old za new

Například:

1
2
3
4
5
soubor="archiv.tar.gz"
echo ${soubor%%.*}    # archiv
echo ${soubor#*.}     # tar.gz
echo ${soubor##*.}    # gz
echo ${#soubor}       # 13

Substituce příkazů — výstup jiného příkazu

Někdy se hodí na příkazový řádek zadat výstup jiného příkazu. To je možné buď pomocí zpětných apostrofů `prikaz` nebo pomocí konstrukce $(prikaz)

Aktuální datum v zadaném formátu vypisuje program date. Například

1
2
$ date +%Y_%m_%d
2013_10_17

Pokud bych požadoval vytvořit adresář, který se bude jmenovat podle aktuálního data použiji příkaz:

1
$ mkdir $(date +%Y_%m_%d)

nebo

1
$ mkdir `date +%Y_%m_%d`

Shell nejprve spustí příkaz date a jeho výstup potom umístí na příkazový řádek za mkdir.

Pokud by bylo potřeba datum uložit do proměnné bude to vypadat asi takto:

1
$ datum=$(date +%Y_%m_%d)

nebo

1
$ datum=`date +%Y_%m_%d`

Aritmetické/Matematické výrazy

Matematický výraz lze zapsat pomocí konstrukce $(( výraz )) (postaru to bylo $[ výraz ]).

1
2
3
4
5
6
$ N=123
$ N=$(( 2*$N ))
$ echo $N
246
$ echo $(( 2**10 ))
1024

Rozvinutí složených závorek

Shell rozepíše složené závorky ještě před spuštěním příkazu. Existují dva tvary.

Výčet — čárkou oddělené hodnoty, které se dosadí za (nebo před) přiléhající řetězec:

1
2
$ ls adresar{1,2,3}
$ ls adresar1 adresar2 adresar3

Rozsah — dva krajní body oddělené ..; shell dopočítá vše mezi nimi:

1
2
3
4
$ echo {1..5}
1 2 3 4 5
$ echo {a..e}
a b c d e

Lze přidat i krok:

1
2
$ echo {1..10..2}
1 3 5 7 9

Čísla lze zarovnat nulami — stačí zapsat krajní bod s nulou na začátku:

1
2
$ echo {01..05}
01 02 03 04 05

Závorky lze skládat za sebou — každá se rozepíše nezávisle a výsledky se zkombinují:

1
2
3
$ echo {0,1}{0,1}{0,1}
000 001 010 011 100 101 110 111
$ echo 192.168.32.{1..8}

Typické použití v praxi:

1
2
3
$ mkdir -p projekt/{src,tests,docs}
$ cp soubor.txt soubor.txt.bak
$ cp soubor{.txt,.txt.bak}        # totéž, kratší zápis

Globbing — expanze jmen souborů/adresářů

Někdy je výhodné pro zápis jmen souborů použít žolíkové znaky (Wildcard character). Pod pojmem žolíkové znaky rozumíme následující konstrukce:

Vzor Význam
\Z znak »Z«
* libovolný počet libovolných znaků
? právě jeden libovolný znak
[abcd] znak a nebo b nebo c nebo d
[a-d] znak a nebo b nebo c nebo d
[a-de] znak a nebo b nebo c nebo d nebo e
[a-bc-d] znak a nebo b nebo c nebo d
[-ab] znak a nebo b nebo -
[^a-ez] libovolný znak vyjma a,b, c, d, e,z
[-a-c^z] jeden ze znaků a,b, c, z, ^,-
~ domovský adresář
~jmeno domovský adresář uživatele »jmeno«

Znak ~ nepatří mezi Glob znaky, ale logicky její použití zapadá do této sekce. Proto ji zmiňuji zde.

Konstrukci v hranatých závorkách odpovídá vždy jeden znak z dané množiny. Znak - slouží pro zkrácený zápis množiny. Pokud je uveden na začátku [- nebo na konci -] odpovídá znaku -. Znak ^ uveden hned na začátku [^ slouží jako negace. Pokud je uveden jinde odpovídá znaku ^.

Uvozování

Je důležité zdůraznit, že expanzi provádí shell nikoliv program. Pokud napíše uživatel mv * adresar, je to shell a nikoliv program mv, kdo nahradí hvězdičku seznamem všech souborů v aktuálním adresáři.

Pokud chceme předat nějakému příkazu skutečně hvězdičku nebo jiný metaznak se zvláštním významem (např. mezera je metaznak, který slouží k oddělování parametrů), je nutné předřadit mu zpětné lomítko jmeno\ souboru nebo dát metaznaky do uvozovek "jmeno souboru" nebo apostrofů 'jmeno souboru'.

Shell při zpracování příkazové řádky apostrofy, uvozovky a zpětná lomítka odstraní, ale jejich obsah ponechá nezměněný. V apostrofech nemění vůbec nic, ale v uvozovkách stále nahrazuje proměnné a konstrukce, které začínají znakem $. Přesné chování při zpracování příkazové řádky a seznam znaků se speciálním významem závisí na konkrétním shellu.

Shell tedy rozlišuje tři způsoby, jak zabránit expanzi (nebo její části):

Zápis Chování
\Z escapuje jediný znak Z — žádná expanze tohoto znaku
'řetězec' apostrofy — žádná expanze, vše doslova
"řetězec" uvozovky — provádí se expanze $, ` a $(); žolíky se neexpandují

Klasická past je dělení na slova — shell po expanzi proměnné rozdělí výsledek na slova podle mezer:

1
2
3
$ soubor="jmeno souboru"
$ ls $soubor       # hledá dvě věci: "jmeno" a "souboru"
$ ls "$soubor"     # hledá jeden soubor "jmeno souboru"

Proto je dobrou praxí proměnné vždy uzavírat do uvozovek: "$PROMENA".

Na samém konci zpracování příkazové řádky shell apostrofy, uvozovky a zpětná lomítka odstraní — spuštěný program je v argumentech neuvidí.


Související posty