Korte uitleg: Handige gereedschappen bij shell scripts.

Korte uitleg: Handige gereedschappen bij shell scripts.

Bij het schrijven van shell scrips (en we nemen bash als voorbeeld), blijkt dat aan de ene kant veel programmeertaal constructies zoals if … then … else en loops, beschikbaar zijn, maar veel hulproutines, zoals andere talen die ter beschikking stellen, ontbreken. Dat komt omdat allerlei handige akties door losse programma’s (gereedschappen/tools) kunnen worden gedaan. Dit alles volgens het Unix principe: maak geen honderdkoppige monsters, maar maak een tool dat één ding kan en dan heel erg goed kan.

Het nadeel hiervan is dat die tools niet in de man pagina van bash staan. Ze hebben natuurlijk wel ieder zelf een man pagina, maar hoe weet je dat ze bestaan en waar ze goed voor zijn. Daarom wil ik hier een lijstje presenteren van tools en waar ze handig voor zijn. Je moet daarbij bedenken dat veel van deze tools bij verstek lezen van stdin en uitvoeren naar stdout. Ze zijn dus bij uitstek geschikt om met pipes aan elkaar te koppelen. Ik zal daar ook voorbeelden van geven.

Vergeet niet om de man pagina van een tool te bestuderen. Ik geeft hieronder alleen hints. Je zult zelf de details moeten uitvinden.

SHELL BUILTIN COMMANDS

Deze zijn eigenlijk niet het onderwerp van deze Korte uitleg omdat ze in de man pagina van bash zijn te vinden. Maar bekijk die lijst dan ook eens. Er zitten o.a. de commando’s in om van de terminal (stdin) te lezen (read en readarray) en er (stdout) naar te schrijven (echo en printf). En met redirect en pipes zijn dat dus de meest gebruikte commando’s om gegevens naar en van je script te sturen.

grep

Leest een aantal regels tekst en filtert deze op inhoud.
Voorbeeld: om uit de mounttabel alleen die regels te filteren die een mount van een echt mass-storage apparaat geven:

henk@boven:~> mount | grep '^/dev'
/dev/sda2 on / type ext4 (rw,relatime,data=ordered)
/dev/sda3 on /home type ext4 (rw,relatime,data=ordered)
henk@boven:~>

Let op het “pattern” dat hier wordt gebruikt. Het wordt uitgebreid beschreven in man grep onder Regular Expressions. Veel mogelijkheden, en niet altijd makkelijk te overzien. Ook is het het beste om het door quoting van shell interpretatie af te schermen.

Nog een combinatie met een bash loop:

henk@boven:~> mount | grep '^/dev' | while read DEV X MP X; do echo $MP "ligt op" $DEV; done
/ ligt op /dev/sda2
/home ligt op /dev/sda3
henk@boven:~>

tr

Vervangt of verwijdert tekens uit text:

henk@boven:~> mount | grep '^/dev' | tr -d '()' | tr ',' ' '
/dev/sda2 on / type ext4 rw relatime data=ordered
/dev/sda3 on /home type ext4 rw relatime data=ordered
henk@boven:~

cut

Knipt stukjes uit regels en laat die zien. Het volgende laat de velden 3 en 5 zien waarbij de velden gescheiden zijn door spaties:

henk@boven:~/test> mount |  grep '^/dev' | cut -d ' ' -f 3,5
/ ext4
/home ext4
henk@boven:~/test>

head / tail

Laat het begin (head) of het eind (tail) van een aantal regels zien. Het aantal kun je opgeven, dus voor de laatste vijf regels van dmesg:

henk@boven:~/test> dmesg | tail -n 5
   50.896609] Bluetooth: BNEP filters: protocol multicast
   50.896628] Bluetooth: BNEP socket layer initialized
   60.757940] fuse init (API version 7.22)
  599.860654] perf samples too long (2506 > 2500), lowering kernel.perf_event_max_sample_rate to 50000
 3367.565707] perf samples too long (5003 > 5000), lowering kernel.perf_event_max_sample_rate to 25000
henk@boven:~/test>

Handig is ook een + voor het aantal regels. Dat geeft de eerste regel van waar tot het eind wordt doorgegeven. Daarmee kun je dus de kopregel(s) uit een lijst verwijderen:

henk@boven:~/test> ps
  PID TTY          TIME CMD
23087 pts/2    00:00:00 bash
23674 pts/2    00:00:00 ps
henk@boven:~/test> ps | tail -n +2
23087 pts/2    00:00:00 bash
23677 pts/2    00:00:00 ps
23678 pts/2    00:00:00 tail
henk@boven:~/test>

wc

Telt het aantal regels, woorden en tekens dat langskomt. Dus:

henk@boven:~/test> wc -l /etc/passwd
35 /etc/passwd
henk@boven:~/test>

laat zien dat er 35 gebruikers in /etc/passwd zijn geconfigureerd.

sed

De stream editor. In tegenstelling tot een interactieve editor voert deze editor de opgegeven wijzigingscommando’s uit op de langskomende regels.

henk@boven:~/test> sed -e 's/#.*$//' -e '/^       ]*$/d' /etc/resolv.conf
search xs4all.nl
nameserver 194.109.6.66
nameserver 194.109.9.99
nameserver 194.109.104.104
henk@boven:~/test>

Hier zijn twee sed akties (aangegeven door -e):

  1. In iedere regel worden alle strings die beginnen met een # tot aan het eind van de regel weggegooid.
  2. Iedere regel die alleen spaties en tabs bevat (er staat en spatie en een tab tussen de en de ]) wordt verwijderd.

Let op. Er zijn wel veel configuratiebestanden waarin een # commentaar aangeeft, maar dat is beslist niet altijd zo!

Er zijn veel mogelijkheden in sed om regels te selecteren en om akties op de geselecteerde regels uit te voeren. Neem de tijd om de documentatie te bekijken en dingen uit te proberen.

awk

Nog veel uitgebreider is awk. Eigenlijk is dit een complete programmeertaal om akties op regels uit te voeren. Sommigen zweren erbij omdat het “alles kan”. Als je er eenmaal handig in bent is kennelijk de neiging om awk dan ook voor alles te gebruiken, ook in plaats van de hierboven genoemde gereedschappen. En zelf in plaats van veel shell constructies.

Een kwestie van smaak.

sort

Soms is het nodig/handig om regels te sorteren. Het programma sort heeft natuurlijk een logische naam en waarschijnlijk had je al uit jezelf geprobeerd of dat bestaat als je het nodig had. Maar ik vermeld het toch maar.

Behalve simpel op ASCII sorteren kan ook op velden gesorteerd worden en zijn vele combinaties mogelijk.

Handig is vaak ook de -u optie, die als er meer regels hetzelfde zijn, er slechts één doorlaat.

cmp

Vergelijkt twee bestanden en geeft de verschillen aan. In een script is het handig omdat de returncode 0 is bij gelijkheid en 1 bij ongelijkheid. Gebruik je dan de -s optie om de uitvoer te onderdrukken.

Er is een vergelijkbaar tool diff, maar dat is beter geschikt om interactief verschillen in text bestanden te vinden (bijv. waarin verschillen twee versies van een configuratie bestand).

Ik heb onlangs pv (pipeviewer) herontdekt. Voorheen maakte ik mijn USB installatie sticks zo:

dd if=openSUSE-Versie.iso of=/dev/sdX bs=4M

Dat heeft als nadeel dat je op geen enkel moment kunt zien “hoever-ie er mee is”.
Nu doe ik 't zo:

pv openSUSE-Versie.iso | dd of=/dev/sdX

Doordat er in openSUSE een aantal opties default aan staan, zie ik niet alleen een simpele voortgangs"balk", maar ook de snelheid waarmee naar USB geschreven wordt en een ETA (Expected Time of Arrival :D.).

En awk, da’s voor mij vroeger onder UNIX. Het is waar, Henk, met awk kan alles. Jaren niet gebruikt, maar ik vond 't makkelijker om wat complexere dingen met awk te doen dan met bijv. sed.

Nog één: diff. Heel handig als je het veschil tussen bestanden wilt zien, of de uitkomst ervan wilt gebruiken, bijv

diff log-van-gisteren log-van-eergisteren > gisteren-gedaan

Het commando zet het verschil tussen een logbestand van gisteren en het logbestand van eergisteren in het bestand gisteren-gedaan.

Omdat ik voornamelijk tools heb gegeven die binnen scripts handig zijn te gebruiken, heb ik voor cmp en niet voor diff gekozen. Misschien moet ik diff erbij vermelden, maar dan niet direct voor gebruik in scripts.

pv is kennelijk leuk voor interactief gebruik (en voor mensen die niet gewoon iets kunnen starten en dan iets leuks gaan doen tot het klaar is ;)), mar pv maakt het volgens mij juist ongeschikt on in een script te gebruiken. En daar zijn de genoemde tools voor.

En natuurlijk hoort echo er ook bij. Toen ik begon met scripten leerde ik om eerst maar elk commando ook te laten echoën. Later nog veel gebruikt, ook om bijv. te loggen
Een voorbeeldje in een primitief backup script:


#! /bin/bash
echo "Starten backup"
echo "Backup gestart" >> /var/log/backup`date`
cp -r -v -p /home /disk/home-backup
echo "Backup klaar"
echo "Backup klaar >> /var/log/backup`date`

stuurt tekst zowel naar 't scherm als naar een logbestand met een datumtoevoeging. Nooit zo gebruiken, kijk eerst met “man date” hoe je 't een beetje netjes kunt doen.

Ik heb ook echo niet opgenomen omdat het gewoon een onderdeel (build-in) van bash is. Het uitgangspunt van de ze Korte uitleg is om dingen aan te reiken die NIET in de bash man page zijn te vinden, ook niet zo logisch van naam zijn dat je ze niet onmiddelijk vindt met man logische-naam (maar ik heb sort, volgens mij zo’n logische naam toch gemoemd). Bovendien moeten ze gereedschap zijn dat hard nodig is om shell scripts (die vaak over tekstverwerking gaan) te kunnen schrijven. Net zoals je in een technisch-wetenschappelijke programmeertaal subroutines voor sinus, cosinus, enz. nodig hebt.

Ik zit nu te broeden op een Korte uitleg met commando’s om informatie te krijgen over hardware en software op je systeem. Ook daarvoor geldt: volgens je gevoel moeten ze bestaan, maar hoe zouden ze die dingen genoemd hebben? Als je de naam eenmaal weet kom je met de man page wel verder. Maar wat is de naam???

Ik heb enkele wijzigingen aangebracht:

  • verwezen naar de sectie SHEL BUILTIN COMMANDOS (en dus naar read en echo);
  • diff vermeld als alternatief voor cmp speciaal voor text bestanden (hoewel diff bij niet textbestanden ongeveer reageert zoals cmp) en voor interactief gebruik.