- Overview
- Teksti vs. mikä tahansa
- Kaksoisviittomat argumenttien ympärillä
- Miksi kirjata komennot historiaan ennen niiden suorittamista?
- Ratkaisu
- Nelle’s Pipeline: Skriptin luominen
- Muuttujat komentosarjan komentosarjoissa
- Ratkaisu
- Löydä pisin tiedosto, jolla on annettu tiedostopääte
- Ratkaisu
- Skriptin lukemisen ymmärtäminen
- Skriptien virheenkorjaus
- Ratkaisu
- Kärkikohdat
Overview
Teaching: 30 min
Exercises:Tavoitteet
Kirjoita komentosarjakomentosarja, joka suorittaa komennon tai komentosarjan kiinteälle tiedostojoukolle.
Suorita komentosarjakomentosarja komentoriviltä.
Kirjoita komentotulkkiskripti, joka toimii käyttäjän komentorivillä määrittelemälle tiedostojoukolle.
Luo putkilinjoja, jotka sisältävät sinun ja muiden kirjoittamia komentotulkkiskriptejä.
Viimein olemme valmiita näkemään, mikä tekeekään komentotulkkiskriptistä niin tehokkaan ohjelmointiympäristön.Otamme komennot, joita toistamme usein, ja tallennamme ne tiedostoihin,jotta voimme myöhemmin suorittaa kaikki nämä toiminnot uudelleen kirjoittamalla yhden komennon.Historiallisista syistä tiedostoon tallennettua komentojoukkoa kutsutaan yleensä komentosarjakirjoitukseksi,mutta älkää erehtykö: nämä ovat itse asiassa pieniä ohjelmia.
Aloitetaan palaamalla osoitteeseen
molecules/
ja luomalla uusi tiedostomiddle.sh
, josta tulee komentosarjamme:$ cd molecules$ nano middle.sh
Komennolla
nano middle.sh
avataan tiedostomiddle.sh
tekstinkäsittelyohjelmassa ’nano’ (joka toimii komentosuorittimessa).Jos tiedostoa ei ole olemassa, se luodaan.voimme käyttää tekstieditoria suoraan tiedoston muokkaamiseen – lisäämme yksinkertaisesti seuraavan rivin:head -n 15 octane.pdb | tail -n 5
Tämä on muunnelma aiemmin rakentamastamme putkesta:se valitsee tiedoston
octane.pdb
rivit 11-15.Muista, ettemme suorita sitä vielä komentona:laitamme komennot tiedostoon.Sitten tallennamme tiedoston (
Ctrl-O
nanossa) ja poistumme tekstieditorista (Ctrl-X
nanossa).Tarkista, että hakemistossamolecules
on nyt tiedosto nimeltämiddle.sh
.Kun olemme tallentaneet tiedoston, voimme pyytää komentotulkkia suorittamaan sen sisältämät komennot.komentotulkkimme nimi on
bash
, joten suoritamme seuraavan komennon:$ 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
Totta kai skriptimme tuloste on täsmälleen se, minkä saisimme, jos suorittaisimme tuon putkilinjan suoraan.
Teksti vs. mikä tahansa
Kutsumme yleensä Microsoft Wordin tai LibreOffice Writerin kaltaisia ohjelmia ”tekstieditoriksi”, mutta meidän on oltava hieman varovaisempia, kun kyse on ohjelmoinnista. Microsoft Word käyttää oletusarvoisesti
.docx
-tiedostoja tallentaakseen paitsi tekstiä, myös muotoilutietoja fonteista, otsikoista ja pian. Tätä lisätietoa ei tallenneta merkkeinä, eikä se merkitse mitäänhead
:n kaltaisille työkaluille: ne odottavat syöttötiedostojen sisältävän vain tavallisen tietokonenäppäimistön kirjaimia, numeroita ja välimerkkejä. Ohjelmia muokattaessa on siis joko käytettävä tavallisen tekstin editoria tai huolehdittava siitä, että tiedostot tallennetaan tavallisena tekstinä.Entä jos haluamme valita rivejä mielivaltaisesta tiedostosta?Voisimme muokata
middle.sh
:a joka kerta tiedostonimen muuttamiseksi,mutta se veisi luultavasti enemmän aikaa kuin komennon kirjoittaminen uudelleen komentotulkissa ja sen suorittaminen uudella tiedostonimellä.Sen sijaan muokataanmiddle.sh
ja tehdään siitä monipuolisempi:$ nano middle.sh
Korvaa nyt ”nanossa” teksti
octane.pdb
erikoismuuttujalla nimeltä:
head -n 15 "" | tail -n 5
Komentosarjan sisällä
tarkoittaa ’komentorivin ensimmäistä tiedostonimeä (tai muuta argumenttia)’.Voimme nyt ajaa skriptimme näin:
$ 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
tai eri tiedostoon näin:
$ 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
Kaksoisviittomat argumenttien ympärillä
Samasta syystä kuin laitamme silmukkamuuttujan kaksoissulkulausekkeiden sisälle,jos tiedostonimi sattuu sisältämään välilyöntejä,ympäröimme
kaksoissulkulausekkeilla.
Tällä hetkellä meidän on muokattava
middle.sh
joka kerta, kun haluamme säätää palautettavien rivien valikoimaa. Korjataan tämä määrittämällä skriptimme käyttämään sen sijaan kolmea komentoriviargumenttia. Ensimmäisen komentoriviargumentin () jälkeen jokaiseen muuhun antamaamme argumenttiin pääsee käsiksi erikoismuuttujien
,
,
kautta, jotka viittaavat vastaavasti ensimmäiseen, toiseen ja kolmanteen komentoriviargumenttiin.
Tiedostaen tämän voimme käyttää lisäargumentteja määrittelemään
head
:lle jatail
:lle välitettävien rivien alueen:$ nano middle.sh
head -n "" "" | tail -n ""
Voitamme nyt suorittaa:
$ 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
Muuttamalla komentomme argumentteja voimme muuttaa skriptimme käyttäytymistä:
$ 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
Tämä toimii,mutta seuraavalta
middle.sh
lukijalta saattaa kestää hetken tajuta, mitä se tekee.Voimme parantaa skriptiämme lisäämällä sen alkuun kommentteja:$ nano middle.sh
# Select lines from the middle of a file.# Usage: bash middle.sh filename end_line num_lineshead -n "" "" | tail -n ""
Kommentti alkaa
#
-merkillä ja jatkuu rivin loppuun.Tietokone ei huomioi kommentteja, mutta ne ovat korvaamattomia, koska ne auttavat ihmisiä (tuleva itsesi mukaan lukien) ymmärtämään ja käyttämään skriptejä.Ainoa varoitus on, että joka kerta, kun muutat skriptiä,sinun on tarkistettava, että kommentti on edelleen oikea:selitys, joka lähettää lukijan väärään suuntaan, on pahempi kuin ei selitystä ollenkaan.Mitä jos haluamme käsitellä monta tiedostoa yhdellä putkella?Jos esimerkiksi haluamme lajitella
.pdb
-tiedostomme pituuden mukaan, kirjoittaisimme:$ wc -l *.pdb | sort -n
koska
wc -l
luettelee tiedostojen rivien lukumäärän(muista, ettäwc
tarkoittaa ”sanojen lukumäärää”, ja-l
-option lisääminen tarkoittaa sen sijaan ”rivien lukumäärää”)jasort -n
lajittelee asiat numeerisesti.Voisimme laittaa tämän tiedostoon, mutta silloin se lajittelisi aina vain luettelon.pdb
tiedostoista nykyisessä hakemistossa.Jos haluamme saada lajitellun listan muunkinlaisista tiedostoista,tarvitsemme keinon saada kaikki nämä nimet skriptiin.Emme voi käyttää,
ja niin edelleen,koska emme tiedä kuinka monta tiedostoa on.Sen sijaan käytämme erikoismuuttujaa
$@
,joka tarkoittaa:’Kaikki komentorivin argumentit komentosarjakomentosarjalle’.Meidän pitäisi myös laittaa$@
kaksinkertaisten lainausmerkkien sisään käsittelemään välilyöntejä sisältäviä argumentteja("$@"
on erityinen syntaksi ja vastaa""
""
…).Tässä on esimerkki:
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
Esimerkki tämäntyyppisestä tiedostosta on
data-shell/data/animal-counts/animals.txt
.Voidaan käyttää komentoa
cut -d , -f 2 animals.txt | sort | uniq
tuottamaan yksilöivä laji kohdassaanimals.txt
. Jotta tutkijan ei tarvitsisi kirjoittaa tätä komentosarjaa joka kerta, hän voi halutessaan kirjoittaa sen sijaan komentotulkkiskriptin.Kirjoita komentotulkkiskripti nimeltä
species.sh
, joka ottaa komentoriviargumentteina minkä tahansa määrän tiedostojen nimiä ja tulostaa yllä olevan komennon muunnelmalla luettelon jokaisessa kyseisessä tiedostossa esiintyvistä yksilöllisistä lajeista erikseen.Sen sijaan, että kirjoittaisimme ne uudelleen(ja mahdollisesti väärin)voimme tehdä näin:$ history | tail -n 5 > redo-figure-3.sh
Tiedosto
redo-figure-3.sh
sisältää nyt: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
Kunhan olemme hetken työskennelleet editorissa poistaaksemme komentojen järjestysnumerot,ja poistaneet viimeisen rivin,jossa kutsuimme komentoa
history
,meillä on täysin tarkka tietue siitä,miten loimme tuon kuvan.Miksi kirjata komennot historiaan ennen niiden suorittamista?
Jos suoritat komennon:
$ history | tail -n 5 > recent.sh
Tiedoston viimeinen komento on itse komento
history
, ts,komentotulkki on lisännythistory
komentolokiin ennen sen varsinaista suorittamista. Itse asiassa komentotulkki lisää aina komennot lokiin ennen niiden suorittamista. Miksi luulet sen tekevän näin?Ratkaisu
Jos jokin komento aiheuttaa jonkin komennon kaatumisen tai roikkumisen, voi olla hyödyllistä tietää, mikä tämä komento oli, jotta ongelmaa voidaan tutkia.Jos komento kirjattaisiin vasta sen suorittamisen jälkeen, meillä ei olisi tallenteita viimeisimmästä suoritetusta komennosta kaatumisen sattuessa.
Käytännössä useimmat ihmiset kehittävät komentotulkkiskriptejä ajamalla komentoja komentotulkkikehotteessa muutaman kerran varmistaakseen, että ne tekevät oikean asian, ja tallentamalla ne sitten tiedostoon uudelleenkäyttöä varten.Tämä työskentelytyyli antaa ihmisille mahdollisuuden kierrättää kaiken sen, mitä he saavat selville tiedoistaan ja työnkulustaan, yhdellä kutsulla komentoon
history
ja pienellä muokkauksella, jolla siistitään tulosteet, ja tallentamalla ne komentotulkkiskriptiksi.Nelle’s Pipeline: Skriptin luominen
Nellen esimies vaati, että kaikki hänen analyysinsä on oltava toistettavissa. Helpoin tapa tallentaa kaikki vaiheet on skripti.
Palaamme ensin Nellen datahakemistoon:
$ cd ../north-pacific-gyre/2012-07-03/
Nelle ajaa editorin ja kirjoittaa seuraavan:
# Calculate stats for data files.for datafile in "$@"do echo $datafile bash goostats $datafile stats-$datafiledone
Nelle tallentaa tämän tiedostoon nimeltä
do-stats.sh
, jotta hän voi nyt tehdä analyysin ensimmäisen vaiheen uudestaan kirjoittamalla:$ bash do-stats.sh NENE*.txt
Hän voi tehdä myös näin:
$ bash do-stats.sh NENE*.txt | wc -l
siten tulosteena on vain käsiteltyjen tiedostojen lukumäärän sijaan käsiteltyjen tiedostojen nimet.
Yksi huomionarvoinen asia Nellen skriptissä on se, että se antaa sen suorittajan päättää, mitä tiedostoja se käsittelee.Hän olisi voinut kirjoittaa sen seuraavasti:
# Calculate stats for Site A and Site B data files.for datafile in NENE*.txtdo echo $datafile bash goostats $datafile stats-$datafiledone
Erityksenä on se, että tämä valitsee aina oikeat tiedostot: Nellen ei tarvitse muistaa sulkea pois Z-tiedostoja.Haittapuolena on se, että se valitsee aina vain nämä tiedostot – Nelle ei voi käyttää sitä kaikkiin tiedostoihin (Z-tiedostot mukaan lukien) tai G- tai H-tiedostoihin, joita hänen kollegansa Antarktiksella tuottavat, ilman että hän joutuisi muokkaamaan skriptiä.Jos hän haluaisi olla seikkailunhaluisempi, hän voisi muuttaa komentosarjaa niin, että se tarkistaisi komentoriviargumentit ja käyttäisi
NENE*.txt
, jos niitä ei ole annettu.Tämä tietysti tuo mukanaan toisen kompromissin joustavuuden ja monimutkaisuuden välille.Muuttujat komentosarjan komentosarjoissa
Kuvittele, että sinulla on hakemistossa
molecules
komentosarjan komentosarja nimeltäscript.sh
, joka sisältää seuraavat komennot:head -n tail -n
Ollessasi hakemistossa
molecules
kirjoitat seuraavan komennon:bash script.sh '*.pdb' 1 1
Minkä seuraavista tulosteista odotat näkeväsi?
- Kaikkien hakemistossa
molecules
olevien tiedostojen ensimmäisten ja viimeisten rivien väliset rivit, jotka päättyvät.pdb
:een- Kaikkien hakemistossa
molecules
olevien tiedostojen ensimmäisten ja viimeisten rivien väliset rivit, jotka päättyvät.pdb
:een- . hakemistossa
- Kunkin hakemistossa
molecules
olevan tiedoston ensimmäinen ja viimeinen rivi- Virhe, koska ympärillä on lainausmerkit
*.pdb
Ratkaisu
Oikea vastaus on 2.
Erikoismuuttujat $1, $2 ja $3 edustavat komentoriviargumentteja, jotka on annettu skriptille, joten ajettavat komennot ovat:
$ 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
Komentotulkki ei laajenna
'*.pdb'
:tä, koska se on lainausmerkkien ympäröimä.Näin ollen skriptin ensimmäinen argumentti on'*.pdb'
, joka laajennetaan skriptissähead
:llä jatail
:llä.
Löydä pisin tiedosto, jolla on annettu tiedostopääte
Kirjoita komentotulkkiskripti nimeltä
longest.sh
, joka ottaa argumentteikseen hakemiston nimen ja tiedostopäätteen ja tulostaa sen tiedoston nimen, jolla on eniten rivejä kyseisessä hakemistossa ja jolla on kyseinen pääte. Esimerkiksi:$ bash longest.sh /tmp/data pdb
tulostaisi
/tmp/data
:ssä olevan.pdb
-tiedoston nimen, jossa on eniten rivejä.Ratkaisu
# 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
Putken ensimmäinen osa,
wc -l /*. | sort -n
, laskee kunkin tiedoston rivit ja lajittelee ne numeerisesti (suurin viimeisenä). Jos tiedostoja on useampia kuin yksi,wc
antaa myös viimeisen yhteenvetorivin, joka ilmoittaa kaikkien tiedostojen rivien kokonaismäärän. Käytämmetail-n 2 | head -n 1
:tä heittääksemme tämän viimeisen rivin pois.Käyttämällä
wc -l /*. | sort -n | tail -n 1
näemme lopullisen yhteenvetorivin: voimme rakentaa putkistomme paloittain, jotta voimme olla varmoja, että ymmärrämme tulosteen.Skriptin lukemisen ymmärtäminen
Tässä kysymyksessä tarkastelemme jälleen kerran hakemistoa
data-shell/molecules
.Tämä sisältää useita.pdb
-tiedostoja mahdollisten muiden luomiesi tiedostojen lisäksi.Selitä, mitä kukin seuraavista kolmesta skriptistä tekisi, kun ne ajetaan nimelläbash script1.sh *.pdb
,bash script2.sh *.pdb
jabash script3.sh *.pdb
.Skripti 1 tulostaisi luettelon kaikista tiedostoista, joiden nimessä on piste.Skriptille annettuja argumentteja ei käytetä missään kohtaa skriptissä.
Skripti 2 tulostaisi kolmen ensimmäisen tiedoston sisällön, joiden tiedostopääte on
.pdb
.,
ja
viittaavat vastaavasti ensimmäiseen, toiseen ja kolmanteen argumenttiin.
Skripti 3 tulostaisi kaikki skriptin argumentit (eli kaikki
.pdb
-tiedostot),jota seuraa.pdb
.$@
viittaa kaikkiin komentosarjakomentosarjalle annettuihin argumentteihin.cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb
cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb
Skriptien virheenkorjaus
Esitettäkö, että olet tallentanut seuraavan skriptin tiedostoon nimeltä
do-errors.sh
Nellennorth-pacific-gyre/2012-07-03
hakemistoon:# Calculate stats for data files.for datafile in "$@"do echo $datfile bash goostats $datafile stats-$datafiledone
Kun suoritat sen:
$ bash do-errors.sh NENE*.txt
Tuloste on tyhjä.Jos haluat selvittää syyn, suorita skripti uudelleen käyttämällä
-x
-vaihtoehtoa:bash -x do-errors.sh NENE*.txt
Mitä tuloste näyttää sinulle?Mikä rivi on vastuussa virheestä?
Ratkaisu
Vaihtoehto
-x
saa aikaan sen, ettäbash
ajetaan debug-tilassa.Tämä tulostaa jokaisen komennon suorituksen yhteydessä, mikä auttaa sinua paikallistamaan virheet.tässä esimerkissä näemme, ettäecho
ei tulosta mitään. Olemme tehneet kirjoitusvirheen silmukkamuuttujan nimessä, ja muuttujaadatfile
ei ole olemassa, joten se palauttaa tyhjän merkkijonon.Kärkikohdat
Tallenna komentoja tiedostoihin (joita yleensä kutsutaan komentosarjan skripteiksi) uudelleenkäyttöä varten.
bash
suorittaa tiedostoon tallennetut komennot.
$@
viittaa kaikkiin komentosarjan komentoriviargumentteihin.
,
jne, viittaavat ensimmäiseen komentoriviargumenttiin, toiseen komentoriviargumenttiin jne.
Sijoita muuttujat lainausmerkkeihin, jos arvoissa saattaa olla välilyöntejä.
Käyttäjien antaminen päättää, mitä tiedostoja käsitellään, on joustavampaa ja johdonmukaisempaa sisäänrakennettujen Unix-komentojen kanssa.