Pokud zavoláme nějaký program v shellu, z několika prvních bytů spouštěcího souboru se určí jakým způsobem se program spustí. U interpretovaných jazyké se zde nachází značka „#!“ zvaná hashbang nebo shebang, za ní následuje absolutní cesta k interpretru jazyka, může obsahovat i parametry pro daný interpretr.
bash | Python |
---|---|
#!/bin/bash ...shellové příkazy |
#!/usr/bin/env python ...pythnové příkazy |
U bashe se předpokládá většinou cesta jako /bin/bash, ale u pythonu to může být /usr/bin/python nebo /usr/local/bin/python a jiné, takže, zde se využije příkaz env, který spustí příkaz v modifikovaném prostředí a python najde v systémových cestách $PATH.
Aby váš skript fungoval nezapomeňte přidělit mu spouštěcí příznak: $ chmod +x ./myscript.sh
bash | Python |
---|---|
$ bash -c "echo Hello World" |
$ python -c "print 'Hello World'" |
V případě bashe, příkazy které používáme mohou být příkazy vestavěné (např. if, for, trap, return, export, declare,... viz man bash), uživatelem definované funkce a nebo programy v systémových cestách operačního systému (ls, stat, date, chmod, chown,...). Existence a parametry těchto příkazů jsou definované normou POSIX, ale v GNU/Linux často obsahují některé rozšiřující vlastnosti. Systémové cesty jsou definované v proměnné prostředí $PATH a oddělené dvoujtečkou, pokud spouštíme program, nacházející se mimo tuto cestu, je nutné použít absolutní nebo relativní cestu k němu. Pokud chceme do našeho skriptu načíst jako modul jiný shellový skript použije se příkaz source a nebo zkráceně tečka „.“.
Python má sadu základních příkazů a další příkazy jsou v modulech pythoní standardní knihovny, ty načítáme pomocí import, pro adminy se nejvíce hodí tyto moduly:
Po naimportování modulu pak příkazy z modulů uvozujeme názvem modulu, např. sys.argv, os.mkdir("/tmp/temp"), atd. Pokud bychom, je chtěli použít rovnou můžeme provést: from os import * a tím si vše vložíme do základního jmenného prostoru a jméno modulu se nemusí uvádět.
bash | Python |
---|---|
#!/bin/bash source /etc/functions |
#!/usr/bin/env import re, sys, os, subprocess |
Bash jednotlivé příkazy odděluje řádky a nebo středníkem, příkazové bloky mají svá definované uvození, např. if-fi, case-esac, for do-done,... odsazování není povinné, pouze doporučené pro zlepšení čitelnosti kódu. Dlouhé příkazy můžeme rozdělovat tak, že před konec řádku dáme obrácené lomítko.
Python také jednotlivé příkazy odděluje řádky a nebo středníkem, ale bloky musejí být odsazené a právě to, co je uvnitř bloku, to se řídí odsazením. Jako odsazení je nutno použít jeden nebo víc mezer či tabelátorů, nejčastěji to bývají 4 mezery.
bash | Python |
---|---|
for i in {0..10} do echo $i echo $((i*2)) done for i in {0..10}; do echo $i; echo $((i*2)); done |
for i in range(0,10): print i print i*2 for i in range(0,10): print i; print i*2; |
Bash má pro vytvoření proměnné striktní syntaxi: NAZEV=10, NAZEV="hodnota" – mezi názvem, operátorem přiřazení a hodnotou nesmí být žádná mezera. Má to svojí výhodu, pokud v rozsáhlejším skriptu potřebujeme najít, kde se mění hodnota proměnné, stačí grepnout NAZEV[+]\?= (většinou by stačilo jenom NAZEV=, ale abychom byli přesní, tento regex pokrývá i operátor +=, která přidává data do řetězce nebo pole). K proměnné pak přistupujeme přes prefixový dolar, tj. $NAZEV nebo ${NAZEV}. Proměnná může být celé číslo, řetězec, dále datový typ pole a nebo asociativní pole (hash).
Python přiřazuje proměnnou obvyklým způsobem, přiřazení je znak rovná se „=“. Dále pak pomocí operátorů přiřazení s operací, např. a += 10, tj. a = a + 10, stejných jako v C. V bashi můžeme tyto operátory využít pouze v matematickém režimu.
bash | Python |
---|---|
date +%Y%m%d%H%M # proveď příkaz var=$(date +%Y%m%d%H%M) # Přesměrování chybového výstupu var2=$(ls non_existent_file 2gt;&1) |
subprocess.call(['date', '+%Y%m%d%H%M']) # položky z CLI rozdělené do pole var = subprocess.check_output("date +%Y%m%d%H%M", shell=True) # Nenulový návratový kód příkazu shellu způsobí chybové hlášní funkce check_output() var2 = subprocess.check_output("ls non_existent_file; exit 0", stderr=subprocess.STDOUT, shell=True) |
Pozor na možné bezpečnostní riziko, při použití řetězce s příkazem namísto pole s odděleným názvem a parametry příkazu. V prvním případě, pokud je zde použit vstup od uživatele, ten by mohl za středník vložit své příkazy a provádět něco nekalého!
Bash rozlišuje použití 'jednoduchých' a "dvojitých" závorek, v prvním případě nejsou interpretovány speciální znaky a escape sekvence (např. $, \n, \t,...). Pokud řetězec neuzavřete do závorek vůbec, jsou bílé znaky potlačeny.
Python mezi typem závorek nerozlišuje. Pokud nechceme nic interpretovat použijeme surové řetězce (raw strings), např. r"\n", R'\n', nebo ur"\n", UR'\n' pro Unicode. Pokud chceme vypsat normální string jako raw, použijeme repr(string).
bash | Python |
---|---|
a="Hello" a+=" World\!" ${#a} # délka řetězce ${a:0:1} # vrátí 'H', podřetězec délky 1 na pozici 0 ${a:6:5} # od pozice 6, podřetězec délky 5 "World" printf 'Hello%.0s' {1..3} # opakování 3× printf "%d" \'A # vypíše ASCII hodnotu znaku 'A' printf \\$(printf '%03o' 65) # převede ASCII 65 na znak s="jedna dva tři"; pole=( $s ) # rozdělí string na prvky pole ${s/jedna/nula} # nahraď první výskyt ${s//a/B} # nahraď všechny výskyty echo 'měšťánek' | sed 's/[[:lower:]]*/\U&/' # převede malá na velká # Zjištění počtu podřetězců je dosti krkolomné s="aaa;baaa;c;daaa;e"; s2=${s//[!aaa]/}; s3=${s2//aaa/a}; echo ${#s3} echo 'hellow world' | rev # vypiš pozpátku printf -v s "%03d %s" 10 'Test data' # do proměnné $s uloží '010 Test data' s=`printf "%03d %s" 10 'Test data'` # tento způsob je pomalejší než předchozí |
a = 'Hello' a += " World!" len(a) a[0] a[6:6+5] # od pozice 6 do pozice 11 'Hello' * 3 # v Pythonu můžeme takto napřímo ord(A) chr(65) pole = "jedna dva tři".split() "jedna dva tři".replace('jedna', 'nula', 1) "jedna dva tři".replace('a', 'B') print U'měšťánek'.upper() # pozor na prefix U pro Unicode # v Pythonu zavoláme count() s="aaa;baaa;c;daaa;e"; s.count('aaa') 'hello world'[::-1] s = "%03d %s" % ( 10, 'Test data' ) s = "{0:03d} {1!s}".format( 10, 'Test data' ) # formátování řetězce pomocí format() |
Bash obsahuje funkce echo a printf, echo může mít navíc parametr -e pro interpretování escape sekvencí a -n pro potlačení výpisu znaku pro nový řádek. Funkce printf je ekvivalentem stejné funkce z std. C knihovny, protože chování a přepínače echa se mohou lišit napříč unixovými platformami je vhodnější použít echo.
Python obsahuje funkci print, ve verzi 3+, je pak povinné uzavřít parametry do kulatých závorek. Funkce print může svým použitím připomínat printf, ale má víc variant a navíc formátovací řětězec a data jsou oddělená „%“ nebo je pro string použita metoda format(), která vrací string a ten je pak použit jako vstupní parametr pro print.
bash | Python |
---|---|
a="Hello" b="World" # inicializace proměnných může být na jednom řádku echo $a$b # výstupem je HelloWorld echo $a $b # výstupem je Hello World printf "%s %s\n" $b $a # výstupem je World Hello echo 'Error!' 1gt;&2 # přesměřování do chybového výstupu (stderr) p=("Toto" "je" "obsah" "pole") # inizializace pole $p # Předefinováním IFS můžeme upravit oddělovač na výstupu, IFS je nutné obnovit OLDIFS="$IFS"; IFS=, ; echo "${p[*]}"; IFS="$OLDIFS" # výstup je Toto,je,obsah,pole |
a="Hello"; b="World" print a + b # je nutné použít sjednocení řetězců print a, b # vypsané proměnné oddělené bílým znakem print "%s %s" % (b, a) print "{1} {0}".format(a, b) # funkce format řeší řetězec, preferovaný způsob pro Python 3 sys.stderr.write('Error!') p=["Toto", "je", "obsah", "pole"] # Zde stačí prohnat iterovatelnou proměnnou funkcí join() print ','.join(p) |
Bash na rozdíl od jiných shellů podporuje jednorozměrná pole, jejich využití si ukážeme na příkladech. K jednotlivým prvkům se přistupuje přes indexy začínající od nuly, pole je možné spojovat, přidávat do nich nové prvky, případně je rušit pomocí funkce unset.
Python v základu obsahuje datovou strukturu list (seznam), s tou pracujeme podobně jako s polem, ale má i některé další metody navíc (odebrání prvku podle obsahu remove, vrácení indexu podle obsahu index, nebo třídění sort, atd.).
bash | Python |
---|---|
a=( Toje je pole "Prvek 3" 31337 ) # inizializace, oddělovač bílý znak set | grep ^a= # výpis všech proměnných a funkcí omezený na $a, vypadá následovně: a=([0]="Toje" [1]="je" [2]="pole" [3]="Prvek 3" [4]="31337") # jiný způsob inicializace a[3]=${a[3]}", na čtvrtém místě" # změníme jeden prvek echo ${a[*]} # vypíše pole a+=666 # k prvnímu prvku přidá řetězec 666 a+=(666) # přidá prvek 666 (resp. pole s jedním prvkem na konec) for i in "${a[@]}"; do echo $i; done | sort # setřídí pole (další možnosti viz 'man sort') for i in "${a[@]}"; do echo $i; done | tac # vypíše pozpátku echo ${#a[*]} # vypíše počet prvků echo ${a[*]//666/777} # ve výpisu nahradí všechny 666 za 777 |
a = [ 'Toje', 'je', 'pole', 'Prvek 3', 31337 ] print a # vypíše pole a[3]=a[3] + ", na čtvrtém místě" print str(a).decode('unicode-escape') # pro interpretaci UTF-8 sekvencí takto a+=666 # skončí chybou 'int' object is not iterable a[0]+="666" # k prvnímu prvku přidá řetězec a.append(666) print sorted(a) # vypíše setřídění seznam a.sort() # setřídí prvky 'in-place' print list(reversed(a)) # vypíše pozpátku, funkce reversed() vrací iterátor a ten pak musíme přetypovat na list a.reverse() # obrátí prvky 'in-place' print len(a) # |
Poznámka 1.: Jedna z velkých změn v syntaxi Pythonu 3 je to, že funkce print musí mít parametry uzavřené v kulatých závorkách, potom musíme použít např. print(len(a)). Také se obejdeme bez nutnosti nějak explicitně vyžadovat Unicode, takže stačí: print(str(a)).
Poznámka 2.: Pokud budete v zdrojovém kódu Pythonu 2.x používat diakritiku, je nutné nadefinovat její kódování a to hned na začátku, pomocí tagu „# -*- coding: kódování -*-“ v komentáři:
#!/usr/bin/env python # -*- coding: utf-8 -*- print 'To je ale nepříjemnost!'
Bez tohoto tagu, se dočkáte chybové hlášky:
File "./skript.py", line 3 SyntaxError: Non-ASCII character '\xc5' in file ./pok2.py on line 3, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details
Bash od verze 4.0 podporuje asociativní pole (hash), to jsou pole, která nejsou indexovány celými čísly, ale pomocí klíčů. Proměnnou s tímto datovým typem je nutné definovat pomocí declare -A název.
Python nazývá asociativní pole slovníky (dictionaries).
bash | Python |
---|---|
declare -A hash hash['key']='hodnota' hash[other key]=777 # v bash klíč nemusí být v uvozovkách set | grep ^hash= # vypsání definice hashe hash=([key]="hodnota" ["other key"]="777" ) ${!hash[*]} # všechny klíče ${hash[*]} # všechny hodnoty |
hash = dict() hash['key'] = 'hodnota' hash['other key']=777 print(hash) # vypsání hashe: {'other key': 777, 'key': 'hodnota'} hash.keys() # vrátí list s klíči hash.values() # vrátí list s hodnotami |
Bash přesměrovává standardní výstup (stdout) do souboru pomocí operátoru „> soubor“, pokud soubor neexistuje a uživatel ma práva, pak je vytvořen, pokud existuje předtím je vynulován. Pro přidání výstupu na konec souboru se používá „>> soubor“. Čtení ze souboru má víc možností.
Python pracuje se soubory podobně jako ostatní programovací jazyky, soubor se musí otevřít (open), můžeme z něj číst po bytech (read), po řádcích (readline), načíst celý do proměnné (readlines), atd. Často si můžeme vypomoct tím, že skript vypisuje vše do stdout, ale když ho pak spouštíme z shellu, přesměřujeme jeho výstup zde: ./skript.py > soubor.txt.
bash | Python |
---|---|
echo 'Hello World!' > soubor.txt exec 3>soubor.txt # soubor pro zápis, přes deskriptor 3 echo 'Hello World!' 1>&3 # stdout přesmeruje do fd 3 exec 3>&- # uzavření deskriptoru sync # příkaz OS pro vynucený zápis bufferů |
f = open('soubor.txt', 'w') # možnosti r, w, rb, w+, r+ print>>f, 'Hello World!' # Python 2.x print('Hello World!', file=f) # Python 3.x f.close() # zavření souboru f.flush() # vyprázdni souborový buffer |
Bash používá svislítko „|“ pro spojování příkazů, kdy ten který vypisuje data do stdout je posílá dalšímu, který je načte ze standardního vstupu (stdin), zpracuje a zase pošle dál. V příkazové řádce unixů takto pracuje velké množství programů, kterým se říká filtry. Pokud náš skript má také fungovat jako filtr, použijeme příkaz read, který normálně načítá vstup až do znaku nový řádek, ten uloží do proměnné a uvnitř while-cyklu, pak provádíme operace s obsahem proměnné.
Python má funkce pro meziprocesovou komunikaci v modulu subprocess, obsahuje obdobu systémových volání a také objekt Popen s metodami pro řízení toku dat. Popen.communitate() vrací tuple (stdoutdata, stderrdata), takže Popen.communitate(...)[0], pak čte data ze stdout. Popen.communicate() ukládá data do mezipaměti a jeho použítí není doporučeno pro velký/nekonečný objem dat.
bash | Python |
---|---|
ls | while read i # načítá po řádcích
do
echo "Načteno: $i"
done
|
# Načte výstup do proměnné
filelist = subprocess.Popen("ls", shell=True, stdout=subprocess.PIPE).communicate()[0]
for i in filelist.split('\n') :
print "Načteno: " + i
|
Bash má vestavěnou podporu pro rozšířené regulání výrazy (extended regex), využijeme je spolu s operátorem „=~“. Dále můžeme využít i utility jako grep, sed pro zpracování textu.
Python v modulu re importuje podporu rozšířených regulárních výrazů a nabízí i operace podobné těm z Perlu.
bash | Python |
---|---|
a="LinuxDays 2014"
regex='(.*)Days ([0-9]{4})' # lepší uložit do proměnné, mezera dělala problémy
if [[ $a =~ $regex ]] ; then
echo Načtený řetězec: \"${BASH_REMATCH[0]}\"
echo OS: \"${BASH_REMATCH[1]}\"
echo Rok: \"${BASH_REMATCH[2]}\"
fi
|
a = "LinuxDays 2014" ro = re.compile('(.*)Days ([0-9]{4})') result = ro.match(a) # vytvoří nový objekt odpovídající regexu if result : # pokud objekt existuje, pak proveď následující print('Načtený řetězec: "%s" ' % result.group(0) ) print('OS: "%s" ' % result.group(1) ) print('Rok: "%s" ' % result.group(2) ) |
bash | Python |
---|---|
$0 # název skriptu $1 # první poziční parametr $# # počet parametrů |
sys.argv[0] sys.argv[1] len(sys.argv) |
Poznámka: Více o pozočních parametrech v článku: Zpracování parametrů příkazové řádky v shell-skriptech