- Översikt
- Text vs. Whatever
- Dubbla citattecken runt argument
- Lista Unika arter
- Lösning
- Varför registrera kommandon i historiken innan du kör dem?
- Lösning
- Nelle’s Pipeline: Skapa ett skript
- Variabler i skalskript
- Lösning
- Finn den längsta filen med en given filändelse
- Lösning
- Skriptläsningsförståelse
- Lösningar
- Felsökning av skript
- Lösning
- Nyckelpunkter
Översikt
Undervisning: 30 min
Övningar: 15 minFrågor
Hur kan jag spara och återanvända kommandon?
MålSkriv ett skalskript som kör ett kommando eller en serie av kommandon för en bestämd uppsättning filer.
Kör ett skalskript från kommandoraden.
Skriv ett skalskript som kör en uppsättning filer som definieras av användaren på kommandoraden.
Skapa pipelines som innehåller skalskript som du och andra har skrivit.
Vi är äntligen redo att se vad som gör skalet till en så kraftfull programmeringsmiljö.Vi kommer att ta de kommandon som vi upprepar ofta och spara dem i filerså att vi kan köra om alla dessa operationer igen senare genom att skriva ett enda kommando. av historiska skäl brukar ett gäng kommandon som sparas i en fil kallas för ett skalskript, men missta dig inte: dessa är faktiskt små program.
Låt oss börja med att gå tillbaka till molecules/
och skapa en ny fil, middle.sh
som kommer att bli vårt skalskript:
$ cd molecules$ nano middle.sh
Kommandot nano middle.sh
öppnar filen middle.sh
i textredigeraren ’nano’ (som körs i skalet).Om filen inte finns kommer den att skapas.Vi kan använda textredigeraren för att redigera filen direkt – vi lägger helt enkelt in följande rad:
head -n 15 octane.pdb | tail -n 5
Det här är en variant på pipen som vi konstruerade tidigare: den väljer raderna 11-15 i filen octane.pdb
.Kom ihåg att vi inte kör den som ett kommando ännu: vi lägger kommandona i en fil.
Därefter sparar vi filen (Ctrl-O
i nano) och avslutar textredigeraren (Ctrl-X
i nano).Kontrollera att katalogen molecules
nu innehåller en fil som heter middle.sh
.
När vi har sparat filen kan vi be skalet att utföra de kommandon som den innehåller. vårt skal heter bash
, så vi kör följande kommando:
$ 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
Säkerligen är vårt skriptutgång exakt vad vi skulle få om vi körde den pipelinen direkt.
Text vs. Whatever
Vi brukar kalla program som Microsoft Word eller LibreOffice Writer för ”texteditorer”, men vi måste vara lite mer försiktiga när det gäller programmering. Som standard använder Microsoft Word
.docx
-filer för att lagra inte bara text utan även formateringsinformation om typsnitt, rubriker och snart. Denna extra information lagras inte som tecken och betyder ingenting för verktyg somhead
: de förväntar sig att inmatningsfiler inte innehåller något annat än bokstäver, siffror och interpunktion på ett standardtangentbord. När du redigerar program måste du därför antingen använda en redigeringsprogram för klartext eller vara noga med att spara filer som klartext.
Vad händer om vi vill välja rader från en godtycklig fil?Vi skulle kunna redigera middle.sh
varje gång för att ändra filnamnet, men det skulle antagligen ta längre tid än att skriva kommandot igen i skalet och köra det med ett nytt filnamn.Låt oss istället redigera middle.sh
och göra den mer mångsidig:
$ nano middle.sh
Nu, i ”nano”, ersätter du texten octane.pdb
med den speciella variabel som kallas :
head -n 15 "" | tail -n 5
Inom ett skalskript betyder ”det första filnamnet (eller annat argument) på kommandoraden”.Vi kan nu köra vårt skript så här:
$ 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
eller på en annan fil så här:
$ 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
Dubbla citattecken runt argument
Av samma anledning som att vi placerar loop-variabeln inom dubbla citattecken, om filnamnet skulle råka innehålla blanksteg, omger vi
med dubbla citattecken.
För närvarande måste vi redigera middle.sh
varje gång vi vill justera intervallet av rader som returneras. Låt oss åtgärda detta genom att konfigurera vårt skript så att det i stället använder tre kommandoradsargument. Efter det första kommandoradsargumentet () kommer varje ytterligare argument som vi tillhandahåller att vara tillgängligt via de särskilda variablerna
,
,
, som hänvisar till det första, andra respektive tredje kommandoradsargumentet.
Med vetskap om detta kan vi använda ytterligare argument för att definiera intervallet av rader som ska skickas till head
respektive tail
:
$ nano middle.sh
head -n "" "" | tail -n ""
Vi kan nu köra:
$ 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
Om vi ändrar argumenten till vårt kommando kan vi ändra vårt skriptbeteende:
$ 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
Detta fungerar, men det kan ta nästa person som läser middle.sh
en stund att lista ut vad det gör.Vi kan förbättra vårt skript genom att lägga till några kommentarer överst:
$ nano middle.sh
# Select lines from the middle of a file.# Usage: bash middle.sh filename end_line num_lineshead -n "" "" | tail -n ""
En kommentar börjar med ett #
-tecken och löper fram till radens slut.Datorn ignorerar kommentarer, men de är ovärderliga för att hjälpa människor (inklusive ditt framtida jag) att förstå och använda skript.Den enda invändningen är att varje gång du ändrar skriptet bör du kontrollera att kommentaren fortfarande är korrekt: en förklaring som skickar läsaren i fel riktning är värre än ingen alls.
Hur blir det om vi vill behandla många filer i en enda pipeline?Om vi till exempel vill sortera våra .pdb
-filer efter längd skulle vi skriva:
$ wc -l *.pdb | sort -n
för att wc -l
listar antalet rader i filerna (kom ihåg att wc
står för ”word count” (antal ord), att lägga till alternativet -l
innebär att man i stället räknar ”count lines” (antal rader)) och sort -n
sorterar saker numeriskt.Vi skulle kunna lägga detta i en fil,men då skulle det bara sortera en lista med .pdb
filer i den aktuella katalogen.Om vi vill kunna få en sorterad lista med andra typer av filer behöver vi ett sätt att få in alla dessa namn i skriptet.Vi kan inte använda ,
och så vidare eftersom vi inte vet hur många filer det finns.Istället använder vi den speciella variabeln
$@
som betyder ”Alla kommandoradsargument till skalskriptet”.Vi bör också sätta $@
inom dubbla citattecken för att hantera fallet med argument som innehåller blanksteg ("$@"
är en speciell syntax och motsvarar ""
""
…).
Här är ett exempel:
$ 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
Lista Unika arter
Leah har flera hundra datafiler, som var och en är formaterad på detta sätt:
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
Ett exempel på denna typ av fil finns i
data-shell/data/animal-counts/animals.txt
.Vi kan använda kommandot
cut -d , -f 2 animals.txt | sort | uniq
för att ta fram den unika arten ianimals.txt
. För att slippa skriva denna serie kommandon varje gång kan en forskare välja att skriva ett skalskript i stället.Skriv ett skalskript med namnet
species.sh
som tar ett valfritt antal filnamn som kommandoradsargument och använder en variant av ovanstående kommando för att skriva ut en lista över de unika arter som förekommer i var och en av dessa filer separat.Lösning
# 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
Antag att vi just har kört en serie kommandon som har gjort något användbart – till exempel skapat en graf som vi vill använda i en uppsats. vi vill kunna återskapa grafen senare om vi behöver det, så vi vill spara kommandona i en fil.Istället för att skriva in dem igen (och eventuellt göra fel) kan vi göra så här:
$ history | tail -n 5 > redo-figure-3.sh
Filen redo-figure-3.sh
innehåller nu:
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
Efter en stunds arbete i en editor för att ta bort serienumren på kommandona och för att ta bort den sista raden där vi anropade kommandot history
har vi ett helt korrekt register över hur vi skapade figuren.
Varför registrera kommandon i historiken innan du kör dem?
Om du kör kommandot:
$ history | tail -n 5 > recent.sh
det sista kommandot i filen är själva kommandot
history
, dvs,skalet har lagt tillhistory
i kommandologgen innan det verkligen körs. I själva verket lägger skalet alltid till kommandon i loggen innan det kör dem. Varför tror du att den gör detta?Lösning
Om ett kommando orsakar att något kraschar eller hänger, kan det vara bra att veta vilket kommando det var, för att kunna undersöka problemet.Om kommandot bara registreras efter att det har körts, skulle vi inte ha något register över det senaste kommandot som kördes i händelse av en krasch.
I praktiken utvecklar de flesta människor skalskript genom att köra kommandon vid skalprompten några gånger för att försäkra sig om att de gör rätt, och sedan spara dem i en fil för att återanvända dem.Detta arbetssätt gör det möjligt för människor att återanvända vad de upptäcker om sina data och sitt arbetsflöde med ett enda anrop till history
och lite redigering för att städa upp utgången och spara den som ett skalskript.
Nelle’s Pipeline: Skapa ett skript
Nelles chef insisterade på att alla hennes analyser måste vara reproducerbara. Det enklaste sättet att fånga alla steg är i ett skript.
Först återvänder vi till Nelles datakatalog:
$ cd ../north-pacific-gyre/2012-07-03/
Hon kör editorn och skriver följande:
# Calculate stats for data files.for datafile in "$@"do echo $datafile bash goostats $datafile stats-$datafiledone
Hon sparar detta i en fil som heter do-stats.sh
så att hon nu kan göra om det första steget i sin analys genom att skriva:
$ bash do-stats.sh NENE*.txt
Hon kan också göra så här:
$ bash do-stats.sh NENE*.txt | wc -l
så att resultatet bara är antalet bearbetade filer istället för namnen på de filer som bearbetades.
En sak att notera om Nelles skript är att det låter personen som kör det bestämma vilka filer som skall behandlas.Hon kunde ha skrivit det som:
# Calculate stats for Site A and Site B data files.for datafile in NENE*.txtdo echo $datafile bash goostats $datafile stats-$datafiledone
Fördelen är att detta alltid väljer rätt filer: hon behöver inte komma ihåg att utesluta ”Z”-filerna.Nackdelen är att det alltid väljer just dessa filer – hon kan inte köra det på alla filer (inklusive ”Z”-filerna), eller på ”G”- eller ”H”-filerna som hennes kollegor i Antarktis producerar, utan att redigera manuset.Om hon vill vara mer äventyrlig kan hon ändra skriptet så att det kontrollerar om det finns kommandoradsargument och använder NENE*.txt
om det inte finns några sådana.Detta innebär naturligtvis ytterligare en avvägning mellan flexibilitet och komplexitet.
Variabler i skalskript
I katalogen
molecules
kan du tänka dig att du har ett skalskript med namnetscript.sh
som innehåller följande kommandon:head -n tail -n
I katalogen
molecules
skriver du följande kommando:bash script.sh '*.pdb' 1 1
Vilken av följande utdata skulle du förvänta dig att se?
- Alla rader mellan den första och sista raden i varje fil som slutar på
.pdb
i katalogenmolecules
- Den första och sista raden i varje fil som slutar på
.pdb
i katalogenmolecules
. katalog- Den första och sista raden i varje fil i
molecules
katalogen- Ett fel på grund av citattecken runt
*.pdb
Lösning
Det rätta svaret är 2.
De särskilda variablerna $1, $2 och $3 representerar de kommandoradsargument som ges till skriptet, så att de kommandon som körs är:
$ 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
Skalet expanderar inte
'*.pdb'
eftersom det är inneslutet av citationstecken.Det första argumentet till skriptet är'*.pdb'
som expanderas i skriptet medhead
ochtail
.
Finn den längsta filen med en given filändelse
Skriv ett skalskript som heter
longest.sh
som tar namnet på en katalog och ett filnamnstillägg som argument och skriver ut namnet på den fil med flest rader i den katalogen med det tillägget. Till exempel:$ bash longest.sh /tmp/data pdb
skulle skriva ut namnet på den fil
.pdb
i/tmp/data
som har flest rader.Lösning
# 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
Den första delen av pipelinen,
wc -l /*. | sort -n
, räknar raderna i varje fil och sorterar dem numeriskt (största sist). Om det finns mer än en fil, gerwc
också ut en sista sammanfattningsrad som anger det totala antalet rader i alla filer. Vi användertail-n 2 | head -n 1
för att kasta bort denna sista rad.Med
wc -l /*. | sort -n | tail -n 1
ser vi den slutliga sammanfattningsraden: vi kan bygga upp vår pipeline i bitar för att vara säkra på att vi förstår utgången.
Skriptläsningsförståelse
För den här frågan ska vi återigen titta på katalogen
data-shell/molecules
.Denna innehåller ett antal.pdb
-filer utöver eventuella andra filer som du kan ha skapat.Förklara vad vart och ett av följande tre skript skulle göra när de körs sombash script1.sh *.pdb
,bash script2.sh *.pdb
respektivebash script3.sh *.pdb
.# Script 1echo *.*
# Script 2for filename in do cat $filenamedone
# Script 3echo [email protected]
Lösningar
I varje fall expanderar skalet jokertecknet i
*.pdb
innan den resulterande listan med filnamn skickas som argument till skriptet.Skript 1 skulle skriva ut en lista över alla filer som innehåller en punkt i namnet.Argumenten som skickas till skriptet används faktiskt inte någonstans i skriptet.
Skript 2 skulle skriva ut innehållet i de tre första filerna med filändelsen
.pdb
.,
och
hänvisar till det första, andra respektive tredje argumentet.
Skript 3 skulle skriva ut alla argument till skriptet (dvs. alla
.pdb
-filer), följt av.pdb
.$@
hänvisar till alla argument som ges till ett skalskript.cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb
Felsökning av skript
Antag att du har sparat följande skript i en fil som heter
do-errors.sh
i Nellesnorth-pacific-gyre/2012-07-03
katalog:# Calculate stats for data files.for datafile in "$@"do echo $datfile bash goostats $datafile stats-$datafiledone
När du kör det:
$ bash do-errors.sh NENE*.txt
utgången är tom.För att ta reda på varför kör du skriptet på nytt med alternativet
-x
:bash -x do-errors.sh NENE*.txt
Vad visas i utmatningen?Vilken rad är ansvarig för felet?
Lösning
Optionen
-x
gör attbash
körs i felsökningsläge.Detta skriver ut varje kommando när det körs, vilket hjälper dig att hitta fel.I det här exemplet kan vi se attecho
inte skriver ut något. Vi har gjort ett fel i namnet på loopvariabeln och variabelndatfile
existerar inte och returnerar därför en tom sträng.Nyckelpunkter
Spara kommandon i filer (vanligtvis kallade skalskript) för att kunna återanvända dem.
bash
kör de kommandon som sparats i en fil.
$@
hänvisar till alla kommandoradsargument i ett skalskript.
,
osv, hänvisar till det första kommandoradsargumentet, det andra kommandoradsargumentet osv.
Placera variabler inom citationstecken om värdena kan innehålla blanksteg.
Att låta användarna bestämma vilka filer som ska bearbetas är mer flexibelt och mer konsekvent med inbyggda Unix-kommandon.