Shell-scripts

okt 29, 2021

Overblik

Undervisning: 30 min
Øvelser: 15 min
Spørgsmål

  • Hvordan kan jeg gemme og genbruge kommandoer?

Mål

  • Skriv et shellscript, der kører en kommando eller en serie af kommandoer for et fast sæt filer.

  • Kør et shellscript fra kommandolinjen.

  • Skriv et shellscript, der opererer på et sæt filer, der er defineret af brugeren på kommandolinjen.

  • Opret pipelines, der indeholder shellscripts, som du og andre har skrevet.

  • Vi er endelig klar til at se, hvad der gør shell’en til et så kraftfuldt programmeringsmiljø.Vi vil tage de kommandoer, som vi ofte gentager, og gemme dem i filer, så vi senere kan genudføre alle disse operationer igen ved at skrive en enkelt kommando. af historiske årsager kaldes en række kommandoer, der er gemt i en fil, normalt for et shell-script, men tag ikke fejl: det er faktisk små programmer.

    Lad os starte med at gå tilbage til molecules/ og oprette en ny fil, middle.sh, som vil blive vores shellscript:

    $ cd molecules$ nano middle.sh

    Kommandoen nano middle.sh åbner filen middle.sh i teksteditoren ‘nano'(som kører i shell’en).Hvis filen ikke eksisterer, vil den blive oprettet. vi kan bruge teksteditoren til at redigere filen direkte – vi indsætter blot følgende linje:

    head -n 15 octane.pdb | tail -n 5

    Dette er en variation af den pipe, vi konstruerede tidligere:den vælger linje 11-15 i filen octane.pdb.Husk, at vi ikke kører den som en kommando lige nu:vi lægger kommandoerne i en fil.

    Så gemmer vi filen (Ctrl-O i nano) og afslutter teksteditoren (Ctrl-X i nano).Kontroller, at mappen molecules nu indeholder en fil med navnet middle.sh.

    Når vi har gemt filen, kan vi bede shell’en om at udføre de kommandoer, den indeholder. vores shell hedder bash, så vi kører følgende 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

    Sikkert nok er vores scripts output nøjagtig det samme som det, vi ville få, hvis vi kørte denne pipeline direkte.

    Text vs. Whatever

    Vi kalder normalt programmer som Microsoft Word eller LibreOffice Writer for “teksteditorer”, men vi skal være lidt mere forsigtige, når det kommer tilprogrammering. Som standard bruger Microsoft Word .docx-filer til at gemme ikke kun tekst, men også formateringsoplysninger om skrifttyper, overskrifter og snart. Disse ekstra oplysninger gemmes ikke som tegn og betyder ikke noget for værktøjer som head: de forventer, at inputfiler ikke indeholder andet end bogstaver, cifre og tegnsætning på et standard computertastatur. Når du redigerer programmer, skal du derfor enten bruge en almindelig tekst-editor eller være omhyggelig med at gemme filer som almindelig tekst.

    Hvad sker der, hvis vi vil vælge linjer fra en vilkårlig fil?Vi kunne redigere middle.sh hver gang for at ændre filnavnet, men det ville sandsynligvis tage længere tid end at skrive kommandoen igen i shell’en og udføre den med et nyt filnavn.Lad os i stedet redigere middle.sh og gøre den mere alsidig:

    $ nano middle.sh

    Nu skal du i “nano” erstatte teksten octane.pdb med den særlige variabel kaldet :

    head -n 15 "" | tail -n 5

    Inden for et shellscript betyder “det første filnavn (eller andet argument) på kommandolinjen”.Vi kan nu køre vores script på denne måde:

    $ 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 anden fil på denne måde:

    $ 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

    Dobbelte anførselstegn omkring argumenter

    Af samme grund som vi sætter loop-variablen inden for dobbelte anførselstegn, omgiver vi med dobbelte anførselstegn i tilfælde af, at filnavnet tilfældigvis indeholder mellemrum.

    På nuværende tidspunkt skal vi redigere middle.sh, hver gang vi ønsker at justere det område af linjer, der returneres. Lad os løse det ved at konfigurere vores script til i stedet at bruge tre kommandolinjeargumenter. Efter det første kommandolinjeargument () vil hvert yderligere argument, som vi angiver, være tilgængeligt via de særlige variabler , , , som henviser til henholdsvis det første, andet og tredje kommandolinjeargument.

    Ved kendskab til dette kan vi bruge yderligere argumenter til at definere rækken af linjer, der skal overføres til henholdsvis head og tail:

    $ nano middle.sh
    head -n "" "" | tail -n ""

    Vi kan nu køre:

    $ 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

    Ved at ændre argumenterne til vores kommando kan vi ændre vores scripts opførsel:

    $ 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

    Dette virker, men det kan tage et øjeblik for den næste person, der læser middle.sh, at regne ud, hvad det gør.Vi kan forbedre vores script ved at tilføje nogle 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 starter med et #-tegn og løber til slutningen af linjen.Computeren ignorerer kommentarer, men de er uvurderlige til at hjælpe folk (inklusive dit fremtidige jeg) med at forstå og bruge scripts.Det eneste forbehold er, at hver gang du ændrer scriptet, bør du kontrollere, at kommentaren stadig er korrekt: En forklaring, der sender læseren i den forkerte retning, er værre end ingen overhovedet.

    Hvad sker der, hvis vi vil behandle mange filer i en enkelt pipeline?Hvis vi f.eks. ønsker at sortere vores .pdb filer efter længde, ville vi skrive:

    $ wc -l *.pdb | sort -n

    fordi wc -l angiver antallet af linjer i filerne(husk at wc står for ‘word count’, tilføjelse af -l betyder i stedet ‘count lines’)og sort -n sorterer tingene numerisk.Vi kunne lægge dette i en fil,men så ville den altid kun sortere en liste over .pdb filer i den aktuelle mappe.Hvis vi vil kunne få en sorteret liste over andre typer filer, skal vi have en måde at få alle disse navne ind i scriptet på.Vi kan ikke bruge , osv.fordi vi ikke ved, hvor mange filer der er.I stedet bruger vi den specielle variabel $@,som betyder:’Alle kommandolinjeargumenter til shellscriptet’.Vi skal også sætte $@ inden for dobbelte anførselstegnfor at håndtere tilfælde af argumenter, der indeholder mellemrum("$@" er en særlig syntaks og svarer til "" "" …).

    Her er et eksempel:

    $ 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

    Liste over unikke arter

    Leah har flere hundrede datafiler, som hver især er formateret på denne måde:

    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

    Et eksempel på denne type fil er vist i data-shell/data/animal-counts/animals.txt.

    Vi kan bruge kommandoen cut -d , -f 2 animals.txt | sort | uniq til at fremstille den unikke art i animals.txt. For at undgå at skulle skrive denne række af kommandoer hver gang, kan en forsker vælge at skrive et shellscript i stedet.

    Skriv et shellscript kaldet species.sh, der tager et vilkårligt antal filnavne som kommandolinjeargumenter og bruger en variation af ovenstående kommando til at udskrive en liste over de unikke arter, der optræder i hver af disse filer for sig.

    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

    Sæt, at vi lige har kørt en række kommandoer, som har gjort noget nyttigt – f.eks. som har skabt en graf, som vi gerne vil bruge i en artikel. vi vil gerne kunne genskabe grafen senere, hvis vi har brug for det, så vi vil gemme kommandoerne i en fil.I stedet for at skrive dem ind igen(og potentielt få dem forkert)kan vi gøre dette:

    $ history | tail -n 5 > redo-figure-3.sh

    Filen redo-figure-3.sh indeholder 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 et øjebliks arbejde i en editor for at fjerne løbenumrene på kommandoerne og for at fjerne den sidste linje, hvor vi kaldte kommandoen history, har vi en fuldstændig nøjagtig registrering af, hvordan vi skabte denne figur.

    Hvorfor registrere kommandoer i historikken før du kører dem?

    Hvis du kører kommandoen:

    $ history | tail -n 5 > recent.sh

    den sidste kommando i filen er selve history-kommandoen, dvs,shell’en har tilføjet history til kommandologgen, før den faktisk blev kørt. Faktisk tilføjer shell’en altid kommandoer til loggen, før den kører dem. Hvorfor tror du, at den gør dette?

    Løsning

    Hvis en kommando får noget til at gå ned eller hænge, kan det være nyttigtat vide, hvilken kommando det var, for at kunne undersøge problemet.Hvis kommandoen først blev registreret, efter at den var blevet kørt, ville vi ikke have en registrering af den sidst kørte kommando i tilfælde af et nedbrud.

    I praksis udvikler de fleste mennesker shell-scripts ved at køre kommandoer ved shell-prompten et par gange for at sikre sig, at de gør det rigtige, og derefter gemme dem i en fil til genbrug.Denne arbejdsform giver folk mulighed for at genbruge det, de opdager om deres data og deres arbejdsgang med et kald til historyog en smule redigering for at rense output og gemme det som et shell-script.

    Nelles Pipeline: Oprettelse af et script

    Nelles vejleder insisterede på, at alle hendes analyser skulle kunne reproduceres. Den nemmeste måde at indfange alle trinene på er i et script.

    Først vender vi tilbage til Nelles datamappe:

    $ cd ../north-pacific-gyre/2012-07-03/

    Hun kører editoren og skriver følgende:

    # Calculate stats for data files.for datafile in "$@"do echo $datafile bash goostats $datafile stats-$datafiledone

    Hun gemmer dette i en fil kaldet do-stats.shså hun nu kan gentage den første fase af sin analyse ved at skrive:

    $ bash do-stats.sh NENE*.txt

    Hun kan også gøre dette:

    $ bash do-stats.sh NENE*.txt | wc -l

    Så at resultatet blot er antallet af behandlede filer i stedet for navnene på de filer, der blev behandlet.

    En ting at bemærke ved Nelles script er, at det lader den person, der kører det, bestemme, hvilke filer der skal behandles.Hun kunne have skrevet 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 er, at det altid vælger de rigtige filer: hun behøver ikke at huske at udelukke “Z”-filerne.Ulempen er, at det altid kun vælger disse filer – hun kan ikke køre det på alle filer (inklusive “Z”-filerne) eller på de “G”- eller “H”-filer, som hendes kolleger i Antarktis producerer, uden at redigere scriptet.Hvis hun ville være mere eventyrlysten, kunne hun ændre scriptet til at kontrollere, om der er angivet kommandolinjeargumenter, og bruge NENE*.txt, hvis der ikke er angivet nogen.Dette medfører naturligvis endnu en afvejning mellem fleksibilitet og kompleksitet.

    Variabler i shellscripts

    I mappen molecules forestiller du dig, at du har et shellscript kaldet script.sh, der indeholder følgende kommandoer:

    head -n  tail -n  

    Mens du befinder dig i mappen molecules, skriver du følgende kommando:

    bash script.sh '*.pdb' 1 1

    Hvilket af følgende output ville du forvente at se?

    1. Alle linjer mellem den første og den sidste linje i hver fil, der slutter på .pdbi mappen molecules
    2. Den første og den sidste linje i hver fil, der slutter på .pdb i mappen molecules mappen
    3. Den første og den sidste linje i hver fil i mappen molecules i mappen
    4. En fejl på grund af anførselstegn omkring *.pdb

    Løsning

    Det rigtige svar er 2.

    De specielle variabler $1, $2 og $3 repræsenterer de kommandolinjeargumenter, der er givet til scriptet, således at de kommandoer, der køres, er:

    $ 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

    Shell’en udvider ikke '*.pdb', fordi det er omsluttet af anførselstegn. som sådan er det første argument til scriptet '*.pdb', som bliver udvidet i scriptet med head og tail.

    Find den længste fil med en given udvidelse

    Skriv et shellscript kaldet longest.sh, der tager navnet på en mappe og en filnavns udvidelse som argumenter og udskriver navnet på den fil med flest linjer i den pågældende mappe med den pågældende udvidelse. For eksempel:

    $ bash longest.sh /tmp/data pdb

    vil udskrive navnet på den fil .pdb i /tmp/data, der har flest linjer.

    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ørste del af pipelinen, wc -l /*. | sort -n, tæller linjerne i hver fil og sorterer dem numerisk (den største sidst). Når der er mere end én fil, udsender wc også en sidste oversigtslinje, der viser det samlede antal linjer i alle filer. Vi bruger tail-n 2 | head -n 1 til at smide denne sidste linje væk.

    Med wc -l /*. | sort -n | tail -n 1 ser vi den endelige opsummeringslinje: vi kan bygge vores pipeline op i stykker for at være sikre på, at vi forstår outputtet.

    Script Reading Comprehension

    For dette spørgsmål skal vi endnu en gang se på mappen data-shell/molecules.Denne indeholder en række .pdb-filer ud over eventuelle andre filer, som du måtte have oprettet.Forklar, hvad hvert af de følgende tre scripts ville gøre, når de blev kørt som henholdsvisbash script1.sh *.pdb, bash script2.sh *.pdb og bash script3.sh *.pdb.

    # Script 1echo *.*
    # Script 2for filename in   do cat $filenamedone
    # Script 3echo [email protected]

    Solutions

    I hvert tilfælde udvider shell’en jokertegnet i *.pdb, før den videregiver den resulterende liste af filnavne som argumenter til scriptet.

    Script 1 ville udskrive en liste over alle filer, der indeholder et punktum i deres navn. de argumenter, der er overgivet til scriptet, bruges faktisk ikke noget sted i scriptet.

    Script 2 ville udskrive indholdet af de første 3 filer med en .pdb filudvidelse., og henviser til henholdsvis det første, andet og tredje argument.

    Script 3 ville udskrive alle argumenterne til scriptet (dvs. alle .pdb-filer),efterfulgt af .pdb. $@ henviser til alle de argumenter, der er givet til et shellscript.

    cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb

    Debugging Scripts

    Sæt, at du har gemt følgende script i en fil med navnet do-errors.shi Nelles north-pacific-gyre/2012-07-03mappe:

    # Calculate stats for data files.for datafile in "$@"do echo $datfile bash goostats $datafile stats-$datafiledone

    Når du kører det:

    $ bash do-errors.sh NENE*.txt

    er outputtet tomt.Hvis du vil finde ud af hvorfor, skal du køre scriptet igen med indstillingen -x:

    bash -x do-errors.sh NENE*.txt

    Hvad viser outputtet dig?Hvilken linje er ansvarlig for fejlen?

    Løsning

    Optionen -x får bash til at køre i fejlfindingstilstand.Dette udskriver hver kommando, efterhånden som den køres, hvilket vil hjælpe dig med at finde fejl.I dette eksempel kan vi se, at echo ikke udskriver noget. Vi har lavet en skrivefejl i loop-variabelnavnet, og variablen datfile eksisterer ikke, hvorfor den returnerer en tom streng.

    Nøglepunkter

    • Sparer kommandoer i filer (normalt kaldet shell-scripts) for at kunne genbruges.

    • bash kører de kommandoer, der er gemt i en fil.

    • $@ henviser til alle et shellscript’s kommandolinjeargumenter.

    • , , osv, henviser til det første kommandolinjeargument, det andet kommandolinjeargument osv.

    • Sæt variabler i anførselstegn, hvis værdierne kan indeholde mellemrum.

    • Det er mere fleksibelt og mere konsekvent med indbyggede Unix-kommandoer at lade brugerne bestemme, hvilke filer der skal behandles.

    Skriv et svar

    Din e-mailadresse vil ikke blive publiceret.