Shell Scripts

loka 29, 2021

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 tiedosto middle.sh, josta tulee komentosarjamme:

$ cd molecules$ nano middle.sh

Komennolla nano middle.sh avataan tiedosto middle.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ä hakemistossa molecules 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ään head: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 muokataan middle.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 ja tail: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 -lluettelee tiedostojen rivien lukumäärän(muista, että wc tarkoittaa ”sanojen lukumäärää”, ja -l-option lisääminen tarkoittaa sen sijaan ”rivien lukumäärää”)ja sort -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 kohdassa animals.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ännyt history 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?

  1. Kaikkien hakemistossa molecules olevien tiedostojen ensimmäisten ja viimeisten rivien väliset rivit, jotka päättyvät .pdb:een
  2. Kaikkien hakemistossa molecules olevien tiedostojen ensimmäisten ja viimeisten rivien väliset rivit, jotka päättyvät .pdb:een
  3. . hakemistossa
  4. Kunkin hakemistossa molecules olevan tiedoston ensimmäinen ja viimeinen rivi
  5. 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ä ja tail: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ämme tail-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 ja bash 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.shNellen north-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 muuttujaa datfile 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.

Vastaa

Sähköpostiosoitettasi ei julkaista.