- Přehled
- Text vs. cokoli
- Dvojité uvozovky kolem argumentů
- Seznam jedinečných druhů
- Řešení
- Proč zaznamenávat příkazy do historie před jejich spuštěním?“
- Řešení
- Nelle’s Pipeline: Vytvoření skriptu
- Proměnné ve skriptech shellu
- Řešení
- Najít nejdelší soubor s danou příponou
- Řešení
- Čtení skriptů s porozuměním
- Řešení
- Debugovací skripty
- Řešení
- Klíčové body
Přehled
Výuka: 30 min
Cvičení:Cíle
Napsat shellový skript, který spustí příkaz nebo sérii příkazů pro pevnou sadu souborů.
Spustit shellový skript z příkazového řádku.
Napsat shellový skript, který pracuje se sadou souborů definovaných uživatelem na příkazovém řádku.
Vytvořit pipelines, které zahrnují vámi napsané shellové skripty i skripty jiných uživatelů.
Jsme konečně připraveni zjistit, co dělá shell tak mocným programovacím prostředím.Vezmeme příkazy, které často opakujeme, a uložíme je do souborů, abychom mohli všechny tyto operace později znovu spustit zadáním jediného příkazu. z historických důvodů se svazek příkazů uložených v souboru obvykle nazývá skript shellu, ale nenechte se mýlit: jsou to vlastně malé programy.
Začněme tím, že se vrátíme na molecules/
a vytvoříme nový soubor middle.sh
, který se stane naším shellovým skriptem:
$ cd molecules$ nano middle.sh
Příkaz nano middle.sh
otevře soubor middle.sh
v textovém editoru ‚nano‘ (který se spouští v shellu).Pokud soubor neexistuje, vytvoří se. textový editor můžeme použít k přímé úpravě souboru – jednoduše vložíme následující řádek:
head -n 15 octane.pdb | tail -n 5
Toto je variace na fajfku, kterou jsme zkonstruovali dříve:vybere řádky 11-15 souboru octane.pdb
.Nezapomeňte, že ji zatím nespouštíme jako příkaz:příkazy vkládáme do souboru.
Poté soubor uložíme (Ctrl-O
v nano) a ukončíme textový editor (Ctrl-X
v nano).zkontrolujte, že adresář molecules
nyní obsahuje soubor s názvem middle.sh
.
Po uložení souboru můžeme požádat shell o provedení příkazů, které obsahuje. náš shell se jmenuje bash
, takže spustíme následující příkaz:
$ bash middle.sh
ATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00
Jisté je, že výstup našeho skriptu je přesně takový, jaký bychom dostali, kdybychom tuto pipeline spustili přímo.
Text vs. cokoli
Obvykle říkáme programům jako Microsoft Word nebo LibreOffice Writer „textové editory“, ale pokud jde o programování, musíme být trochu opatrnější. Ve výchozím nastavení používá Microsoft Word soubory
.docx
k ukládání nejen textu, ale také formátovacích informací o písmech, nadpisech a brzy. Tyto dodatečné informace nejsou uloženy jako znaky a pro nástroje jakohead
nic neznamenají: očekávají, že vstupní soubory budou obsahovat pouze písmena, číslice a interpunkční znaménka standardní počítačové klávesnice. Při úpravách programů proto musíte buď používat textový editor, nebo dávat pozor, abyste soubory ukládali jako prostý text.
Co když chceme vybírat řádky z libovolného souboru?Mohli bychom pokaždé upravit middle.sh
a změnit název souboru,ale to by pravděpodobně trvalo déle než zadat příkaz znovu v shellu a spustit ho s novým názvem souboru.Místo toho upravme middle.sh
a udělejme jej univerzálnějším:
$ nano middle.sh
Nyní v rámci „nano“ nahraďte text octane.pdb
speciální proměnnou nazvanou :
head -n 15 "" | tail -n 5
Vnitř shellovém skriptu znamená „první jméno souboru (nebo jiný argument) na příkazovém řádku“.Nyní můžeme náš skript spustit takto:
$ bash middle.sh octane.pdb
ATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00
nebo na jiném souboru takto:
$ bash middle.sh pentane.pdb
ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00
Dvojité uvozovky kolem argumentů
Ze stejného důvodu, proč dáváme proměnnou cyklu do dvojitých uvozovek, pro případ, že by název souboru náhodou obsahoval nějaké mezery, obklopíme
dvojitými uvozovkami.
V současné době musíme upravovat middle.sh
pokaždé, když chceme upravit rozsah vrácených řádků. Napravíme to tak, že náš skript nakonfigurujeme tak, aby místo toho používal tři argumenty příkazového řádku. Po prvním argumentu příkazového řádku () bude každý další argument, který zadáme, přístupný prostřednictvím speciálních proměnných
,
,
, které odkazují na první, druhý, resp. třetí argument příkazového řádku.
Známe-li tuto skutečnost, můžeme pomocí dalších argumentů definovat rozsah řádků, které budou předány head
, respektive tail
:
$ nano middle.sh
head -n "" "" | tail -n ""
Můžeme nyní spustit:
$ bash middle.sh pentane.pdb 15 5
ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00
Změnou argumentů našeho příkazu můžeme změnit chování našeho skriptu:
$ bash middle.sh pentane.pdb 20 5
ATOM 14 H 1 -1.259 1.420 0.112 1.00 0.00ATOM 15 H 1 -2.608 -0.407 1.130 1.00 0.00ATOM 16 H 1 -2.540 -1.303 -0.404 1.00 0.00ATOM 17 H 1 -3.393 0.254 -0.321 1.00 0.00TER 18 1
Toto funguje,ale dalšímu člověku, který si přečte middle.sh
, může chvíli trvat, než zjistí, co to dělá.Náš skript můžeme vylepšit přidáním několika komentářů na začátek:
$ nano middle.sh
# Select lines from the middle of a file.# Usage: bash middle.sh filename end_line num_lineshead -n "" "" | tail -n ""
Komentář začíná znakem #
a vede až na konec řádku. počítač komentáře ignoruje,ale jsou neocenitelné, protože pomáhají lidem (včetně vašeho budoucího já) pochopit a používat skripty.Jedinou výhradou je, že při každé úpravě skriptu byste měli zkontrolovat, zda je komentář stále správný:vysvětlení, které čtenáře pošle špatným směrem, je horší než žádné.
Co když chceme zpracovat mnoho souborů v jedné pipeline?Chceme-li například seřadit naše .pdb
soubory podle délky, zadáme:
$ wc -l *.pdb | sort -n
protože wc -l
vypisuje počet řádků v souborech(připomeňme, že wc
znamená „počet slov“, přidání volby -l
znamená místo toho „počet řádků“)a sort -n
řadí věci číselně.Mohli bychom to dát do souboru,ale pak by to vždy seřadilo pouze seznam .pdb
souborů v aktuálním adresáři.Pokud chceme mít možnost získat setříděný seznam jiných druhů souborů,potřebujeme způsob, jak do skriptu dostat všechna tato jména.Nemůžeme použít ,
a tak dále, protože nevíme, kolik souborů existuje.Místo toho použijeme speciální proměnnou
$@
, která znamená: „Všechny argumenty příkazového řádku shellového skriptu“.Měli bychom také vložit $@
do dvojitých uvozovek, abychom zvládli případ argumentů obsahujících mezery("$@"
je speciální syntaxe a je ekvivalentní ""
""
…).
Tady je příklad:
$ nano sorted.sh
# Sort files by their length.# Usage: bash sorted.sh one_or_more_filenameswc -l "$@" | sort -n
$ bash sorted.sh *.pdb ../creatures/*.dat
9 methane.pdb12 ethane.pdb15 propane.pdb20 cubane.pdb21 pentane.pdb30 octane.pdb163 ../creatures/basilisk.dat163 ../creatures/minotaur.dat163 ../creatures/unicorn.dat596 total
Seznam jedinečných druhů
Leah má několik set datových souborů, z nichž každý je formátován takto:
2013-11-05,deer,52013-11-05,rabbit,222013-11-05,raccoon,72013-11-06,rabbit,192013-11-06,deer,22013-11-06,fox,12013-11-07,rabbit,182013-11-07,bear,1
Příklad tohoto typu souboru je uveden v
data-shell/data/animal-counts/animals.txt
.Můžeme použít příkaz
cut -d , -f 2 animals.txt | sort | uniq
k vytvoření jedinečného druhu vanimals.txt
. Aby vědec nemusel pokaždé vypisovat tuto sérii příkazů, může se místo toho rozhodnout napsat shellový skript.Napište shellový skript s názvem
species.sh
, který jako argumenty příkazového řádku přijímá libovolný početjmen a pomocí variace výše uvedeného příkazu vypíše seznam jedinečných druhů vyskytujících se v každém z těchto souborů zvlášť.Řešení
# Script to find unique species in csv files where species is the second data field# This script accepts any number of file names as command line arguments# Loop over all filesfor file in $@doecho "Unique species in $file:"# Extract species namescut -d , -f 2 $file | sort | uniqdone
Předpokládejme, že jsme právě spustili sérii příkazů, které provedly něco užitečného – například vytvořily graf, který bychom chtěli použít v článku. chtěli bychom mít možnost později tento graf znovu vytvořit, pokud to budeme potřebovat, takže chceme příkazy uložit do souboru.Místo toho, abychom je zadávali znovu(a případně je spletli)můžeme udělat toto:
$ history | tail -n 5 > redo-figure-3.sh
Soubor redo-figure-3.sh
nyní obsahuje:
297 bash goostats NENE01729B.txt stats-NENE01729B.txt298 bash goodiff stats-NENE01729B.txt /data/validated/01729.txt > 01729-differences.txt299 cut -d ',' -f 2-3 01729-differences.txt > 01729-time-series.txt300 ygraph --format scatter --color bw --borders none 01729-time-series.txt figure-3.png301 history | tail -n 5 > redo-figure-3.sh
Po chvilce práce v editoru, kdy odstraníme pořadová čísla příkazů a odstraníme poslední řádek, kde jsme volali příkaz history
,máme zcela přesný záznam o tom, jak jsme tento obrázek vytvořili.
Pokud spustíte příkaz:
$ history | tail -n 5 > recent.sh
posledním příkazem v souboru je samotný příkaz
history
, tj,shell přidal příkazhistory
do protokolu příkazů před jeho skutečným spuštěním. Ve skutečnosti shell přidává příkazy do protokolu vždy před jejich spuštěním. Proč myslíte, že to dělá?“Řešení
Pokud nějaký příkaz způsobí pád nebo zavěšení něčeho, mohlo by být užitečné vědět, jaký to byl příkaz, abychom mohli problém vyšetřit. „Kdyby se příkaz zaznamenával až po spuštění, neměli bychom v případě pádu záznam o posledním spuštěném příkazu.
V praxi většina lidí vyvíjí shellové skripty tak, že několikrát spustí příkazy na výzvu shellu, aby se ujistili, že dělají správnou věc, a pak je uloží do souboru pro opakované použití. tento styl práce umožňuje lidem recyklovat to, co zjistí o svých datech a svém pracovním postupu, jedním voláním history
a trochou úprav vyčistit výstup a uložit jej jako shellový skript.
Nelle’s Pipeline: Vytvoření skriptu
Nellin nadřízený trval na tom, že všechny její analýzy musí být reprodukovatelné. Nejjednodušší způsob, jak zachytit všechny kroky, je skript.
Nejprve se vrátíme do adresáře s Nellinými daty:
$ cd ../north-pacific-gyre/2012-07-03/
Spustí editor a napíše následující:
# Calculate stats for data files.for datafile in "$@"do echo $datafile bash goostats $datafile stats-$datafiledone
Uloží to do souboru s názvem do-stats.sh
tak, aby nyní mohla znovu provést první fázi své analýzy zadáním:
$ bash do-stats.sh NENE*.txt
Může to také udělat takto:
$ bash do-stats.sh NENE*.txt | wc -l
tak, aby výstupem byl pouze počet zpracovaných souborůnež názvy zpracovaných souborů.
Jednou z věcí, kterou je třeba na Nellině skriptu poznamenat, je, že nechává toho, kdo jej spustí, rozhodnout, které soubory se mají zpracovat.Mohla ho napsat takto:
# Calculate stats for Site A and Site B data files.for datafile in NENE*.txtdo echo $datafile bash goostats $datafile stats-$datafiledone
Výhodou je, že takto vždy vybere správné soubory:nemusí pamatovat na vyloučení souborů „Z“.nevýhodou je, že vždy vybere jen tyto soubory – nemůže ho spustit na všechny soubory(včetně souborů „Z“) nebo na soubory „G“ nebo „H“, které vytvářejí její kolegové v Antarktidě,aniž by skript upravila.Pokud by chtěla být odvážnější, mohla by skript upravit tak, aby kontroloval argumenty příkazového řádku a použil NENE*.txt
, pokud žádné nejsou uvedeny.To samozřejmě přináší další kompromis mezi flexibilitou a složitostí.
Proměnné ve skriptech shellu
Představte si, že v adresáři
molecules
máte skript shellu s názvemscript.sh
obsahující následující příkazy:head -n tail -n
Když jste v adresáři
molecules
, zadáte následující příkaz:bash script.sh '*.pdb' 1 1
Který z následujících výstupů byste očekávali?
- Všechny řádky mezi prvním a posledním řádkem každého souboru končícího na
.pdb
v adresářimolecules
- První a poslední řádek každého souboru končícího na
.pdb
v adresářimolecules
. adresář- První a poslední řádek každého souboru končícího v adresáři
molecules
- Chybná odpověď kvůli uvozovkám kolem
*.pdb
Řešení
Správná odpověď je 2.
Speciální proměnné $1, $2 a $3 představují argumenty příkazového řádku zadané skriptu, takže spouštěné příkazy jsou:
$ head -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb$ tail -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb
Pouzdro nerozbalí
'*.pdb'
, protože je uzavřeno uvozovkami, a proto je prvním argumentem skriptu'*.pdb'
, který se v rámci skriptu rozbalí pomocíhead
atail
.
Najít nejdelší soubor s danou příponou
Napište shellový skript s názvem
longest.sh
, který jako argumenty přijme název adresáře a příponu názvu souboru a vypíše název souboru s nejvíce řádky v tomto adresáři s touto příponou. Například:$ bash longest.sh /tmp/data pdb
vypíše název souboru
.pdb
v adresáři/tmp/data
, který má nejvíce řádků.Řešení
# Shell script which takes two arguments:# 1. a directory name# 2. a file extension# and prints the name of the file in that directory# with the most lines which matches the file extension.wc -l /*. | sort -n | tail -n 2 | head -n 1
První část pipeline,
wc -l /*. | sort -n
, spočítá řádky v každém souboru a seřadí je číselně (největší poslední). Pokud existuje více než jeden soubor,wc
vypisuje také závěrečný souhrnný řádek,který udává celkový počet řádků ve všech souborech. Pomocítail-n 2 | head -n 1
tento poslední řádek vyhodíme.Pomocí
wc -l /*. | sort -n | tail -n 1
uvidíme závěrečný souhrnný řádek: můžeme si sestavit pipeline po částech, abychom měli jistotu, že výstupu rozumíme.
Čtení skriptů s porozuměním
Pro tuto otázku uvažujme opět adresář
data-shell/molecules
.Ten kromě případných dalších vytvořených souborů obsahuje řadu souborů.pdb
. vysvětlete, co by každý z následujících tří skriptů udělal, kdyby byl spuštěn jakobash script1.sh *.pdb
,bash script2.sh *.pdb
, respektivebash script3.sh *.pdb
.# Script 1echo *.*
# Script 2for filename in do cat $filenamedone
# Script 3echo [email protected]
Řešení
V každém případě shell expanduje zástupný znak v
*.pdb
před předáním výsledného seznamu jmen souborů jako argumentů skriptu.Skript 1 by vypsal seznam všech souborů, které obsahují ve svém názvu tečku. argumenty předané skriptu nejsou ve skutečnosti nikde ve skriptu použity.
Skript 2 by vypsal obsah prvních 3 souborů s příponou
.pdb
.,
a
odkazují na první, druhý a třetí argument v tomto pořadí.
Script 3 by vypsal všechny argumenty skriptu (tj. všechny soubory s příponou
.pdb
),za nimiž následuje.pdb
.$@
odkazuje na všechny argumenty zadané shellovému skriptu.cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb
Debugovací skripty
Předpokládejme, že jste následující skript uložili do souboru s názvem
do-errors.sh
v adresáři Nellenorth-pacific-gyre/2012-07-03
:# Calculate stats for data files.for datafile in "$@"do echo $datfile bash goostats $datafile stats-$datafiledone
Při jeho spuštění:
$ bash do-errors.sh NENE*.txt
je výstup prázdný.Chcete-li zjistit proč, spusťte skript znovu s použitím volby
-x
:bash -x do-errors.sh NENE*.txt
Co vám ukazuje výstup Který řádek je zodpovědný za chybu?
Řešení
Volba
-x
způsobí, žebash
se spustí v režimu ladění. ten vypíše každý příkaz při jeho spuštění, což vám pomůže najít chyby. v tomto příkladu vidíme, žeecho
nic nevypisuje. Udělali jsme překlep v názvu proměnné smyčky a proměnnádatfile
neexistuje, proto vracíprázdný řetězec.
Klíčové body
Uložení příkazů do souborů (obvykle se nazývají shellové skripty) pro opakované použití.
bash
spustí příkazy uložené v souboru.
$@
odkazuje na všechny argumenty příkazového řádku shellového skriptu.
,
apod, odkazují na první argument příkazového řádku, druhý argument příkazového řádku atd.
Umístěte proměnné do uvozovek, pokud by v jejich hodnotách mohly být mezery.
Nechat uživatele rozhodnout, které soubory se mají zpracovat, je pružnější a konzistentnější s integrovanými příkazy Unixu.
.