- Áttekintés
- Text vs. akármi
- Dupla idézőjelek az argumentumok körül
- List Unique Species
- megoldás
- Miért rögzítjük a parancsokat az előzményekben, mielőtt lefuttatjuk őket?
- megoldás
- Nelle’s Pipeline: Nelle felettese ragaszkodott ahhoz, hogy minden analitikájának reprodukálhatónak kell lennie. Az összes lépés rögzítésének legegyszerűbb módja egy szkript.
- Változók a Shell szkriptekben
- megoldás
- A leghosszabb fájl keresése adott kiterjesztéssel
- megoldás
- Script olvasásértés
- Solutions
- Skriptek hibakeresése
- megoldás
- Kulcspontok
Áttekintés
Tanítás: 30 perc
GYakorlatok:Célok
Írjon egy shell scriptet, amely egy parancsot vagy parancssorozatot futtat egy meghatározott fájlkészletre.
Futtasson egy shell scriptet a parancssorból.
Egy olyan héjszkript írása, amely a felhasználó által a parancssoron meghatározott fájlkészleten dolgozik.
Pipeline-ok készítése, amelyek az ön és mások által írt héjszkripteket tartalmazzák.
Végre láthatjuk, mitől olyan hatékony programozási környezet a shell.Fogjuk azokat a parancsokat, amelyeket gyakran ismétlünk, és fájlba mentjük őket,hogy később egyetlen parancs beírásával újra lefuttathassuk ezeket a műveleteket.Történelmi okokból a fájlba mentett parancsok halmazát általában shell scriptnek nevezik,de ne tévedjünk:ezek valójában kis programok.
Kezdjük azzal, hogy visszamegyünk a
molecules/
-be, és létrehozunk egy új fájlt, amiddle.sh
-at, amely a shell scriptünk lesz:$ cd molecules$ nano middle.sh
A
nano middle.sh
parancs megnyitja amiddle.sh
fájlt a ‘nano’ szövegszerkesztőben (amely a shellben fut).Ha a fájl nem létezik, akkor létrejön.a szövegszerkesztővel közvetlenül is szerkeszthetjük a fájlt – egyszerűen beillesztjük a következő sort:head -n 15 octane.pdb | tail -n 5
Ez a korábban szerkesztett pipának egy változata:a
octane.pdb
fájl 11-15. sorát választja ki.Ne feledjük, hogy még nem parancsként futtatjuk:a parancsokat egy fájlba helyezzük.Ezután elmentjük a fájlt (
Ctrl-O
a nano-ban), és kilépünk a szövegszerkesztőből (Ctrl-X
a nano-ban).Ellenőrizzük, hogy amolecules
könyvtárban most már van egymiddle.sh
nevű fájl.Miután elmentettük a fájlt,megkérhetjük a héjat, hogy hajtsa végre a benne lévő parancsokat.a mi héjunk neve
bash
, így a következő parancsot futtatjuk:$ 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
Egyértelmű, hogy a szkriptünk kimenete pontosan az, amit akkor kapnánk, ha közvetlenül futtatnánk ezt a csővezetéket.
Text vs. akármi
Az olyan programokat, mint a Microsoft Word vagy a LibreOffice Writer általában “szövegszerkesztőnek” nevezzük, de a programozásnál kicsit óvatosabbnak kell lennünk. A Microsoft Word alapértelmezés szerint
.docx
fájlokat használ nemcsak a szöveg, hanem a betűtípusokra, címsorokra és hamarosan a formázási adatok tárolására is. Ezek az extra információk nem karakterek formájában tárolódnak, és nem jelentenek semmit az olyan eszközök számára, mint ahead
: ezek azt várják, hogy a bemeneti fájlok csak a szabványos számítógépes billentyűzet betűit, számjegyeit és írásjeleit tartalmazzák. A programok szerkesztésekor ezért vagy egyszerű szövegszerkesztőt kell használnunk, vagy ügyelnünk kell arra, hogy a fájlokat egyszerű szövegként mentsük el.Mi van, ha egy tetszőleges fájlból akarunk sorokat kiválasztani?Minden egyes alkalommal szerkeszthetjük a
middle.sh
parancsot a fájlnév megváltoztatására,de ez valószínűleg tovább tartana, mintha a parancsot újra begépelnénk a shellben, és új fájlnévvel hajtanánk végre.Ehelyett szerkesszük meg amiddle.sh
-t, és tegyük sokoldalúbbá:$ nano middle.sh
Most a “nano”-ban a
octane.pdb
szöveget helyettesítsük anevű speciális változóval:
head -n 15 "" | tail -n 5
Egy héjszkriptben a
jelentése “az első fájlnév (vagy más argumentum) a parancssorban”.Most már így futtathatjuk a szkriptünket:
$ 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
vagy egy másik fájlon így:
$ 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
Dupla idézőjelek az argumentumok körül
Azért, amiért a ciklusváltozót is dupla idézőjelek közé tesszük, ha a fájlnév véletlenül szóközöket tartalmaz, a
-t is dupla idézőjelekkel vesszük körül.
Pillanatnyilag a
middle.sh
-t kell szerkesztenünk minden alkalommal, amikor módosítani akarjuk a visszaadott sorok tartományát. Oldjuk meg ezt úgy, hogy a szkriptünket úgy állítjuk be, hogy ehelyett három parancssori argumentumot használjon. Az első parancssori argumentum () után minden további általunk megadott argumentum a
,
,
speciális változókon keresztül lesz elérhető, amelyek az első, második, illetve harmadik parancssori argumentumra utalnak.
Azzal, hogy ezt tudjuk, a további argumentumokkal meghatározhatjuk a
head
, illetve atail
számára átadandó sorok tartományát:$ nano middle.sh
head -n "" "" | tail -n ""
Most futtathatjuk:
$ 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
A parancsunk argumentumainak megváltoztatásával megváltoztathatjuk a szkriptünk viselkedését:
$ 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
Ez működik,de a
middle.sh
következő olvasónak egy pillanatba telhet, mire rájön, mit csinál.Javíthatunk a szkriptünkön, ha a tetejére néhány megjegyzést teszünk:$ nano middle.sh
# Select lines from the middle of a file.# Usage: bash middle.sh filename end_line num_lineshead -n "" "" | tail -n ""
A megjegyzés egy
#
karakterrel kezdődik, és a sor végéig tart.A számítógép figyelmen kívül hagyja a megjegyzéseket,de felbecsülhetetlen értékűek, mert segítenek az embereknek (beleértve a leendő önmagadat is) megérteni és használni a szkripteket.Az egyetlen kikötés, hogy minden egyes alkalommal, amikor módosítod a szkriptet,ellenőrizd, hogy a megjegyzés még mindig pontos-e:egy olyan magyarázat, amely rossz irányba tereli az olvasót, rosszabb, mintha egyáltalán nem lenne.Mi van, ha sok fájlt akarunk feldolgozni egyetlen csővezetékben?Ha például a
.pdb
fájljainkat hossz szerint akarjuk rendezni, akkor beírjuk:$ wc -l *.pdb | sort -n
mert a
wc -l
a fájlok sorainak számát sorolja fel(emlékezzünk, hogy awc
a “szószámot” jelenti, a-l
opció hozzáadása a “sorok számát” jelenti helyette)és asort -n
numerikusan rendezi a dolgokat.Ezt beletehetnénk egy fájlba,de akkor mindig csak az aktuális könyvtárban lévő.pdb
fájlok listáját rendezné.Ha azt akarjuk, hogy másfajta fájlok rendezett listáját is megkaphassuk,valahogyan be kell juttatnunk az összes nevet a szkriptbe.Nem használhatjuk a,
és így tovább,mert nem tudjuk, hogy hány fájl van.Ehelyett a
$@
speciális változót használjuk,ami azt jelenti,’All of the command-line arguments to the shell script’.A$@
-t is dupla idézőjelek közé kell tennünk, hogy kezeljük a szóközöket tartalmazó argumentumok esetét("$@"
speciális szintaxis, és egyenértékű a""
""
…).Íme egy példa:
$ 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
List Unique Species
Leah-nak több száz adatállománya van, mindegyik így van formázva:
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
Egy példa az ilyen típusú fájlra a
data-shell/data/animal-counts/animals.txt
-ban található.A
cut -d , -f 2 animals.txt | sort | uniq
paranccsal előállíthatjuk aanimals.txt
-ben szereplő egyedi fajokat. Annak érdekében, hogy ne kelljen minden alkalommal begépelni ezt a parancssort, a tudós írhat helyette egy shell scriptet.Írd meg a
species.sh
nevű shell scriptet, amely tetszőleges számú fájlnevet fogad el parancssori argumentumként, és a fenti parancs egy variációját használja a fenti fájlok mindegyikében külön-külön előforduló egyedi fajok listájának kiírására.megoldás
# 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
Tegyük fel, hogy most futtattunk le egy sor parancsot, amelyek valami hasznosat csináltak – például létrehoztak egy grafikont, amelyet egy dolgozatban szeretnénk használni.Szeretnénk később újra létrehozni a grafikont, ha szükséges,ezért a parancsokat egy fájlba akarjuk menteni.Ahelyett, hogy újra beírnánk őket(és esetleg elrontanánk)ezt tehetjük:
$ history | tail -n 5 > redo-figure-3.sh
A
redo-figure-3.sh
fájl most már tartalmazza: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
Miután egy szerkesztőprogramban egy pillanatra eltávolítjuk a sorszámokat a parancsokból,és az utolsó sort,ahol a
history
parancsot hívtuk,teljesen pontos feljegyzést kapunk arról,hogyan hoztuk létre ezt az ábrát.Miért rögzítjük a parancsokat az előzményekben, mielőtt lefuttatjuk őket?
Ha a következő parancsot futtatjuk:
$ history | tail -n 5 > recent.sh
A fájlban az utolsó parancs maga a
history
parancs, azaz,a héj hozzáadta ahistory
parancsot a parancsnaplóhoz, mielőtt ténylegesen lefuttatta volna. Valójában a shell mindig hozzáadja a parancsokat a naplóhoz, mielőtt lefuttatná őket. Mit gondolsz, miért csinálja ezt?megoldás
Ha egy parancs valaminek az összeomlását vagy leállását okozza, hasznos lehet tudni, hogy mi volt az a parancs, hogy kivizsgálhassuk a problémát.Ha a parancsot csak a futtatás után rögzítenénk, akkor összeomlás esetén nem rendelkeznénk az utolsó futtatott parancsról.
A gyakorlatban a legtöbb ember úgy fejleszt shell szkripteket, hogy néhányszor lefuttat parancsokat a shell prompton, hogy megbizonyosodjon arról, hogy a megfelelő dolgot csinálják, majd elmenti őket egy fájlba, hogy újra felhasználhassa őket.Ez a munkastílus lehetővé teszi, hogy az emberek újrahasznosítsák mindazt, amit felfedeznek az adataikról és a munkafolyamatukról, egyetlen hívással a
history
hoz és egy kis szerkesztéssel, hogy megtisztítsák a kimenetet és shell szkriptként elmentsék.Nelle’s Pipeline: Nelle felettese ragaszkodott ahhoz, hogy minden analitikájának reprodukálhatónak kell lennie. Az összes lépés rögzítésének legegyszerűbb módja egy szkript.
Először térjünk vissza Nelle adatkönyvtárába:
$ cd ../north-pacific-gyre/2012-07-03/
Futtatja a szerkesztőt, és a következőket írja:
# Calculate stats for data files.for datafile in "$@"do echo $datafile bash goostats $datafile stats-$datafiledone
Ezt egy
do-stats.sh
nevű fájlba menti, így most az elemzés első szakaszát újra elvégezheti a beírással:$ bash do-stats.sh NENE*.txt
Ezt is megteheti:
$ bash do-stats.sh NENE*.txt | wc -l
így a kimenet csak a feldolgozott fájlok száma lesz, nem pedig a feldolgozott fájlok neve.
Egy dolog, amit meg kell jegyezni Nelle szkriptjével kapcsolatban, hogy a futtató személy döntheti el, hogy milyen fájlokat dolgozzon fel.Írhatta volna így is:
# Calculate stats for Site A and Site B data files.for datafile in NENE*.txtdo echo $datafile bash goostats $datafile stats-$datafiledone
Az előnye az, hogy ez mindig a megfelelő fájlokat választja ki:nem kell emlékeznie arra, hogy kizárja a “Z” fájlokat.A hátránya az, hogy mindig csak ezeket a fájlokat választja ki – nem tudja futtatni az összes fájlra (beleértve a “Z” fájlokat is),vagy a “G” vagy “H” fájlokra, amelyeket az antarktiszi kollégái készítenek,a szkript szerkesztése nélkül.Ha kalandvágyóbb akar lenni, módosíthatja a szkriptet, hogy ellenőrizze a parancssori argumentumokat, és a
NENE*.txt
-t használja, ha nincs megadva.Természetesen ez újabb kompromisszumot jelent a rugalmasság és a bonyolultság között.Változók a Shell szkriptekben
A
molecules
könyvtárban képzeljük el, hogy van egyscript.sh
nevű Shell szkriptünk, amely a következő parancsokat tartalmazza:head -n tail -n
Amíg a
molecules
könyvtárban van, beírja a következő parancsot:bash script.sh '*.pdb' 1 1
Az alábbi kimenetek közül melyikre számítana?
- A
molecules
könyvtárban- A
molecules
könyvtárban.pdb
-re végződő minden egyes fájl első és utolsó sora közötti sorokat- A
molecules
könyvtárban.pdb
-re végződő minden egyes fájl első és utolsó sorát. könyvtár- A
molecules
könyvtárban- A
molecules
könyvtárban található minden egyes fájl első és utolsó sora- Hiba a
*.pdb
megoldás
A helyes válasz: 2.
A $1, $2 és $3 speciális változók a scriptnek adott parancssori argumentumokat jelentik, így a futtatott parancsok a következők:
$ 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
A shell nem bontja ki a
'*.pdb'
-et, mert idézőjelek közé van zárva.Így a script első argumentuma'*.pdb'
, amely a scriptbenhead
éstail
-vel bővül ki.A leghosszabb fájl keresése adott kiterjesztéssel
Írd meg a
longest.sh
nevű héjszkriptet, amely egy könyvtár nevét és egy fájlnév kiterjesztését veszi argumentumként, és kiírja annak a fájlnak a nevét, amely a legtöbb sort tartalmazza az adott könyvtárban az adott kiterjesztéssel. Például:$ bash longest.sh /tmp/data pdb
a
/tmp/data
-ben található.pdb
fájl nevét írja ki, amelyik a legtöbb sort tartalmazza.megoldás
# 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
A csővezeték első része,
wc -l /*. | sort -n
megszámolja az egyes fájlok sorait, és numerikusan rendezi őket (a legnagyobbat utoljára). Ha egynél több fájl van, awc
egy végső összefoglaló sort is kiad, amely megadja az összes fájl összes sorának számát. Atail-n 2 | head -n 1
segítségével kidobjuk ezt az utolsó sort.A
wc -l /*. | sort -n | tail -n 1
segítségével megnézzük a végső összefoglaló sort: a csővezetékünket darabokra bontva építhetjük fel, hogy biztosan megértsük a kimenetet.Script olvasásértés
Ezzel a kérdéssel kapcsolatban ismét a
data-shell/molecules
könyvtárat nézzük.Ez számos.pdb
fájlt tartalmaz az esetlegesen létrehozott egyéb fájlokon kívül.Magyarázza meg, hogy az alábbi három szkript mindegyike mit csinálna, habash script1.sh *.pdb
,bash script2.sh *.pdb
, illetvebash script3.sh *.pdb
néven futna.# Script 1echo *.*
# Script 2for filename in do cat $filenamedone
# Script 3echo [email protected]
Solutions
A shell minden esetben kibontja a
*.pdb
-ben lévő jokert, mielőtt az így kapott fájlnevek listáját argumentumként átadja a szkriptnek.Az 1. szkript kiírná az összes olyan fájl listáját, amelynek nevében pont van.A szkriptnek átadott argumentumok valójában nem kerülnek felhasználásra a szkriptben.
A 2. szkript az első 3
.pdb
kiterjesztésű fájl tartalmát írná ki.A,
és
az első, a második és a harmadik argumentumra utal.
A 3. script kiírná a script összes argumentumát (azaz az összes
.pdb
fájlt),amelyet a.pdb
követ.A$@
a shell scriptnek adott összes argumentumra utal.cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb
Skriptek hibakeresése
Tegyük fel, hogy a következő szkriptet egy
do-errors.sh
nevű fájlba mentettük Nellenorth-pacific-gyre/2012-07-03
könyvtárában:# Calculate stats for data files.for datafile in "$@"do echo $datfile bash goostats $datafile stats-$datafiledone
A futtatásakor:
$ bash do-errors.sh NENE*.txt
a kimenet üres.Hogy kiderítse, miért, futtassa újra a szkriptet a
-x
opcióval:bash -x do-errors.sh NENE*.txt
Mit mutat a kimenet?Melyik sor felelős a hibáért?
megoldás
A
-x
opció hatására abash
hibakeresési módban fut.Ez minden egyes parancsot kiír, ahogyan az fut, ami segít a hibák felkutatásában.Ebben a példában láthatjuk, hogy aecho
nem ír ki semmit. Elgépeltük a ciklusváltozó nevét, és adatfile
változó nem létezik, ezért üres karakterláncot ad vissza.Kulcspontok
A parancsokat fájlokba (általában héjszkripteknek nevezik) mentjük, hogy újra felhasználhassuk őket.
bash
a fájlba mentett parancsokat futtatja.
$@
egy héjszkript összes parancssori argumentumára utal.
,
stb, az első parancssori argumentumra, a második parancssori argumentumra stb. utal.
A változókat idézőjelbe tesszük, ha az értékek szóközöket tartalmazhatnak.
Az, hogy a felhasználók dönthetik el, milyen fájlokat dolgozzanak fel, rugalmasabb és következetesebb a beépített Unix parancsokkal.