GNU hulpmiddelen voor bestanden
Kort:
Het vorige artikel in deze serie (Fundamentele UNIX commando’s) gaf een algemeen overzicht van Linux. Het was een introductie in de elementen van Linux, om de basisvaardigheden te leren en het besturingssysteem te beheren, maar de gebruiker zal ook de gewone set Unix commando’s willen leren. Door het gebruik van deze commando’s en de shell kun je bijzonder efficiënt bestanden en systemen beheren. Dit artikel behandelt deze geavanceerdere, hoewel nog steeds fundamentele, hulpmiddelen.
Introductie: de UNIX manier van werken
Voordat de commando’s beschreven worden, zou de lezer enkele feiten over hun geschiedenis moeten kennen. Ken Thompson en Dennis Ritchie wilden, toen ze Unix in het begin van de jaren zeventig ontwikkelden, een besturingssysteem maken dat het leven van programmeurs vereenvoudigde. Ze besloten dat de beste manier om dit te bereiken was om een paar eenvoudige hulpmiddelen te definiëren, die extreem goed waren in enkele gespecialiseerde taken. Moeilijkere taken zouden uitgevoerd kunnen worden door deze hulpmiddelen te combineren, waarbij de uitvoer van het ene invoer zou zijn voor het andere.
Dit concept van het sturen van informatie wordt uitgevoerd via de standaard invoer en uitvoer (scherm en toetsenbord). Als gevolg van het bestaan van pipes en redirection (zoals beschreven in het vorige artikel) is het mogelijk om commando’s te combineren.
Het is erg eenvoudig dit te laten zien aan de hand van een voorbeeld. Een gebruiker schrijft:
$ who | grep pepe
who en grep zijn twee verschillende programma’s, samengevoegd met de pipe “|”. who toont een lijst met elke gebruiker die op het moment angelogd is aan de computer. De uitvoer is zoiets als:
$ who
manolo tty1 Dec 22 13:15
pepe ps/2 Dec 22 14:36
root tty2 Dec 22 10:03
pepe ps/2 Dec 22 14:37
De uitvoer bestaat uit 4 velden, gescheiden door tabs. De velden zijn de gebruikersnaam (login), de terminal waaraan iemand ingelogd is, en de datum en het tijdstip van de verbinding.
“grep pepe” zoekt naar regels met de string “pepe”.
En de uitvoer is:
$ who | grep pepe
pepe ps/2 Dec 22 14:36
pepe ps/2 Dec 22 14:37
Misschien ben je meer geïnteresseerd in iets eenvoudigers. Je kunt het aantal op het moment in gebruik zijnde terminals controleren met behulp van het programma wc.
wc telt de tekens, woorden en regels. Nu hoeven we alleen het aantal regels te weten. Daarom gebruiken we de optie -l (lines, MR).
$ who | wc -l
4
$ who | grep pepe | wc -l
2
In het totaal zijn 4 mensen aangelogd, en pepe is aangelogd aan twee terminals.
Nu controleren we hoe vaak antonio is aangelogd:
$ who | grep antonio | wc -l
0
antonio is dus niet aangelogd.
Het ontstaan van GNU utils
Richard Stallman, de stichter van het GNU project, begon een discussie over de controle over het UNIX besturingssysteem. Die controle was op dat moment in handen van een paar grote softwarebedrijven, die de computerwetenschap ervan weerhielden op een natuurlijke manier volwassen te worden. Tijdens zijn periode bij MIT (Massachusetts Institute of Technology), waar hij de emacs editor schreef, ontwikkelde hij een afkeer tegen het feit dat de grote commerciële bedrijven zijn werk namen om daar geld voor te vragen. Hiermee geconfronteerd, besloot hij een project te starten, waarbij de broncode van de software voor iedereen beschikbaar was. Dat was GNU. Het lange-termijn doel was om een volledig open-source besturingssysteem te maken. De eerste stappen betroffen een nieuwe open-source versie van emacs, een C compiler (gcc) en een paar typische UNIX systeem hulpmiddelen. Het zijn deze hulpmiddelen die we in dit artikel behandelen.
grep
Ons eerste voorbeeld toonde de belangrijkste functie van grep. Nu zullen we het gedetailleerder bekijken
De grondvorm grep is
$ grep [-options] pattern files
De meest gebruikte opties (options) zijn:
-n toont het regelnummer voor de overeenkomende regels (nuttig voor het zoeken in grote bestanden, en zo precies te weten waar de overeenkomst te vinden is)
-c toont het aantal gevonden overeenkomsten
-v zoek niet-overeenkomende regels (zoek regels waarin het patroon (pattern) niet aanwezig is)
Het patroon (pattern) is een groep tekens waarop gezocht moet worden. Als er een spatie tussenstaat, moet het patroon tussen dubbele aanhalingstekens (“) gezet worden, om verwarring tussen het patroon en het te doorzoeken bestand (file) te voorkomen. Bijvoorbeeld
$ grep “Hola mundo” file.txt
Als we zoeken naar strings met jokertekens, apostrofs, aanhalingstekens of slashes moeten ze of escaped worden (voorafgegaan door een backslash (\)) of tussen aanhalingstekens geplaatst, om vervanging door de shell te voorkomen.
$ grep \*\”\’\?\< file.txt
Met als mogelijk resultaat:
Esto es una cadena chunga -> *”‘?<
Reguliere expressies
grep en andere GNU utils zijn in staat om geavanceerd te zoeken. Dat is mogelijk door gebruik te maken van reguliere expressies. Reguliere expressies komen overeen met jokertekens in de shell, in die zin dat ze tekens of groepen van tekens voorstellen. Bij de bronnen aan het eind van het artikel vind je een link naar een artikel dat reguliere expressies in detail behandelt.
Een paar voorbeelden:
$ grep c.n
zoek naar elke keer dat een string voorkomst met een c, gevolgd door een willekeurig teken, gevolgd door een t.
$ grep “[Bc]el”
zoek elke keer dat Bel of cel voorkomt.
$ grep “[m-o]ata”
vind die regels waarin mata, nata of oata voorkomen.
$ grep “[^m-o]ata”
Regels met een string eindigend op ata, maar die geen m, n of o als hun eerste letter hebben.
$ grep “^Martin come”
Elke regel die begint met ‘Martin come’. Aangezien ^ voorkomt zonder haakjes eromheen, betekent het het begin van een regel, en geen ontkenning van een groep, zoals in het vorige voorbeeld.
$ grep “durmiendo$”
Alle regels die eindigen op de string ‘durmiendo’. $ staat voor het einde van de regel.
$ grep “^Caja San Fernando gana la liga$”
Regels die precies overeenkomen met wat er staat.
Om de speciale betekening van elk van deze tekens te voorkomen, moet er een backslash voorgezet worden. Bijvoorbeeld:
$ grep “E\.T\.”
zoek de string ‘E.T.’.
Find
Dit commando wordt gebruikt om bestanden te vinden. Een ander LinuxFocus artikel legt het gebruik ervan uit, en het beste wat we kunnen doen is daarnaar verwijzen.
cut & paste
In UNIX wordt informatie gewoonlijk opgeslagen in ASCII bestanden met regel-records, en velden gescheiden door enkele bijzondere tekens, gewoonlijk een tabteken of een dubbele punt (:). Een typische optie is om een paar velden uit een bestand te selecteren en die samen te voegen tot een ander bestand. Voor deze taak zijn cut en paste geschikt.
Laten we als voorbeeld het bestand /etc/passwd nemen, met de gebruikersinformatie. Het bevat 7 velden, gescheiden door “:”. De velden bevatten informatie over de loginnaam, het versleutelde wachtwoord, de gebruikersidentificatie, de naam, de home directory van de gebruiker, en de shell die zijn voorkeur heeft.
Hier een typisch stuk uit zo’n bestand:
root:x:0:0:root:/root:/bin/bash
murie:x:500:500:Manuel Muriel Cordero:/home/murie:/bin/bash
practica:x:501:501:Usuario de practicas para Ksh:/home/practica:/bin/ksh
wizardi:x:502:502:Wizard para nethack:/home/wizard:/bin/bash
Als we nu een combinatie willen maken van de gebruikers met hun shell, moeten we veld 1 en 7 knippen (cut):
$ cut -f1,7 -d: /etc/passwd
root:/bin/bash
murie:/bin/bash
practica:/bin/ksh
wizard:/bin/bash
De optie -f bepaalt de velden die geknipt moeten worden, en -d definiëert het scheidingsteken (tab is standaard).
Ook is het mogelijk een rij velden te selecteren:
$ cut -f5-7 -d: /etc/passwd
root:/root:/bin/bash
Manuel Muriel Cordero:/home/murie:/bin/bash
Usuario de practicas para Ksh:/home/practica:/bin/ksh
Wizard para nethack:/home/wizard:/bin/bash
Al we de uitvoer met ‘>’ naar twee verschillende bestanden hebben gestuurd, en we willen de uitvoer van beide combineren, dan kunnen we het commando paste gebruiken:
$ paste output1 output2
root:/bin/bash:root:/root:/bin/bash
murie:/bin/bash:Manuel Muriel Cordero:/home/murie:/bin/bash
practica:/bin/ksh:Usuario de practicas para Ksk:/home/practica:/bin/ksh
wizard:/bin/bash:Wizard para nethack:/home/wizard:/bin/bash
sort
Laten we aannemen dat we /etc/passwd willen sorteren op het naamveld. Om dit te bereiken, zullen we sort gebruiken, het UNIX sorteer gereedschap.
$ sort -t: +4 /etc/passwd
murie:x:500:500:Manuel Muriel Cordero:/home/murie:/bin/bash
practica:x:501:501:Usuario de practicas para Ksh:/home/practica:/bin/ksh
wizard:x:502:502:Wizard para nethack:/home/wizard:/bin/bash
root:x:0:0:root:/root:/bin/bash
Het is makkelijk te zien dat het bestand gesorteerd is, maar dan wel in de volgorde van de ASCII tabel. Als we geen onderscheid willen maken tussen hoofd- en kleine letters, kunnen we het volgende gebruiken:
$ sort -t: +4f /etc/passwd
murie:x:500:500:Manuel Muriel Cordero:/home/murie:/bin/bash
root:x:0:0:root:/root:/bin/bash
practica:x:501:501:Usuario de practicas para Ksh:/home/practica:/bin/ksh
wizard:x:502:502:Wizard para nethack:/home/wizard:/bin/bash
-t is de optie om het scheidingsteken voor de velden te selecteren, +4 is het aantal velden dat overgeslagen moet worden voordat er gesorteerd wordt, en f betekent dat er gesorteerd dient te worden zonder onderscheid tussen hoofd- en kleine letters.
Je kan nog veel complexere sorteer-opdrachten doen. Zo kunnen we bijvoorbeeld in een eerste stap sorteren op de favoriete shell, en in een tweede stap op de naam:
$ sort -t: +6r +4f /etc/passwd
practica:x:501:501:Usuario de practicas para Ksh:/home/practica:/bin/ksh
murie:x:500:500:Manuel Muriel Cordero:/home/murie:/bin/bash
root:x:0:0:root:/root:/bin/bash
wizard:x:502:502:Wizard para nethack:/home/wizard:/bin/bash
Stel: je hebt een bestand met de namen van mensen die je geld geleend hebt en het bedrag dat je hen geleend hebt. De naam is ‘deudas.txt’:
Son Goku:23450
Son Gohan:4570
Picolo:356700
Ranma 1/2:700
Als je de eerste die je ‘een bezoek moet brengen’ wilt weten, heb je een gesorteerde lijst nodig:
Typ gewoon:
$ sort +1 deudas
Ranma 1/2:700
Son Gohan:4570
Son Goku:23450
Picolo:356700
Dit is echter niet het gewenste resultaat, omdat het aantal tekens niet overal gelijk is. De oplossing is gebruik te maken van de ‘n’ optie:
$ sort +1n deudas
Picolo:356700
Son Goku:23450
Son Gohan:4570
Ranma 1/2:700
De basis opties voor sort zijn:
+n.m springt over de eerste n velden en de volgende m tekens voordat de sort begint.
-n.m stopt met het sorteerproces als het m-de teken van het n-de veld wordt bereikt.
Je volgende parameters kunnen gebruikt worden:
-b slaat initiële spaties over
-d dictionaire sort (gebruikt alleen letters, cijfers en spaties)
-f maakt geen onderscheid tussen hoofd- en kleine letters
-n sorteert numeriek
-r omgekeerde volgorde
wc
Zoals we al eerder zaken is wc een teken-, woord- en regelteller. Default uitvoer bevat het aantal regels, woorden en tekens in het invoerbestand (de invoerbestanden).
De uitvoer kan bepaald worden met de opties:
-l alleen regels (lines)
-w alleen woorden
-c alleen tekens (characters)
Gereedschap om te vergelijken: cmp, comm, diff
Soms moeten we de verschillen tussen twee versies van hetzelfde bestand kennen. Dit wordt voornamelijk gebruikt bij het programmeren, als verschillende mensen aan hetzelfde project werken, en zo de broncode (kunnen) veranderen. Om de verschillen tussen de ene en de andere versie te vinden, kun je deze gereedschappen gebruiken.
cmp is de eenvoudigste. Het vergelijkt twee bestanden en geeft de plek aan waar het eerste verschil optreedt (het geeft een nummer voor het teken, en een het nummer van de regel.)
$ cmp old new
old new differ: char 11234, line 333
comm is iets vooruitstrevender. De uitvoer levert 3 kolommen. De eerste bevat de unieke regels van het eerste bestand, de tweede bevat de unieke regels uit het tweede bestand, en de derde bevat de overeenkomstige regels. Numerieke parameters staan verwijdering van enkele van deze kolommen toe.
-1, -2 en -3 geven aan dat respectievelijk de eerste, tweede en/of derde kolom niet getoond hoeft te worden. Onderstaand voorbeeld geeft alleen die regels die uniek zijn in het eerste bestand en de gemeenschappelijke regels.
$ comm -2 old new
Als laatste, maar zeker niet de minste van de drie, is er diff. Dit is een onmisbaar gereedschap voor programmeerprojecten. Als je al eens een kernel gedownload hebt om te compileren, weet je dat je kunt kiezen uit de broncode van de nieuwe of de patch voor de vorige versie, waarbij deze laatste kleiner is. Deze patch heeft een diff achtervoegsel, wat betekent dat het diff uitvoer is. Dit gereedschap kan editor commando’s (vi, rcs) gebruiken om bestanden identiek te maken. Dit geldt ook voor directories en de archieven waar ze in staan. Het gebruik is duidelijk: je hoeft minder broncode te downloaden (alleen de veranderingen), je voegt de patcht toe, en je compileert. Zonder parameters kan de uitvoer aangeven hoe de veranderingen moeten worden toegepast, zodat de eerste gelijk wordt aan de tweede, met vi commando’s.
$ diff old new
3c3
< The Hobbit
—
> The Lord of the Rings
78a79,87
>Three Rings for the Elven-kings under the sky,
>Seven for the Dwarf-lords in their halls of stone,
>Nine for Mortal Men doomed to die,
>One for the Dark Lord on his dark throne
>In the Land of Mordor where the Shadows lie.
>One Ring to rule them all, One Ring to find them,
>One Ring to bring them all and in the darkness bind them
>In the Land of Mordor where the Shadows lie.
3c3 betekent dat op regel 3 drie regels moeten worden veranderd, waarbij “The Hobbit” verwijderd moet worden, en vervangen door “The Lord of the Rings”. 78a79,87 betekent dat je nieuwe regels moet toevoegen, van regel 79 tot 87.
uniq
uniq verwijdert dubbels. Als we bijvoorbeeld willen weten welke mensen echt verbonden zijn met de computer, moeten we de commando’s who en cut gebruiken.
$ who | cut -f1 -d’ ‘
root
murie
murie
practica
De uitvoer is echter niet helemaal goed. We moeten de tweede keer dat gebruiker murie voorkomt verwijderen. Dit betekent
$ who | cut -f1 -d’ ‘ | sort | uniq
murie
practica
root
De optie -d’ ‘ betekent dat het scheidingsveld een spatie is, omdat de uitvoer dat teken gebruikt in plaats van de tab.
uniq vergelijkt alleen opeenvolgende regels. In ons geval kwamen de 2 keer “murie” direct achter elkaar, maar het had ook anders kunnen zijn. Het is daarom een goed idee om eerst de uitvoer te sorteren met sort voordat je uniq gebruikt.
sed
sed is een van de vreemdere UNIX tools. Het staat voor “stream editor”. Bij het bewerken van tekst op de gewone manier accepteert het programma interactief de wijzigen die de gebruiker aangeeft. sed daarentegen staat ons toe om kleine shell scripts te maken, gelijkend op de batch files in MS-DOS. Het geeft ons dus de moglijkheid om de inhoud van een bestand te veranderen zonder gebruikersinteractie. De mogelijkheden van de editor zijn groot, maar als we dieper op het onderwerp in zouden gaan, zou dit artikel te lang worden. Daarom gaan we voor een korte introductie, en geïnteresseerden kunnen de man en info pagina’s bestuderen.
sed wordt gewoonlijk aangeroepen als:
$ sed ‘command’ files
Neem als voorbeeld een bestand, waarin we elk voorkomen van “Manolo” willen vervangen door “Fernando”. Dat gaat zo:
$ sed ‘s/Manolo/Fernando/g’ file
Via standaard uitvoer krijg je het veranderde bestand. Als je de resultaten wilt bewaren, redirect je het met “>”.
Veel gebruikers zullen het gewone search & replace vi commando herkennen. In feite zijn de meeste “:” commando’s (die ex aanroepen) sed commando’s.
Gewoonlijk bestaan sed instructies uit een of twee adressen (om regels te selecteren) en het commando dat uitgevoerd moet worden. Het adres kan een regel zijn, een aantal regels, of een patroon.
De meest gebruikte commando’s zijn:
Commando Actie
——– —–
a\ voeg een regel toe na de geadresseerde regel in de invoer
c\ verander de geadresseerde regels, schrijf de regel
d verwijder de regel(s)
g verander het patroon overal, in plaats van alleen de eerste
keer dat het voorkomt
i\ voeg regels toe na de geadresseerde regels
p druk de huidige regel af, zelfs als de -n optie gebruikt wordt
q stop (verlaat het programma) als de geadresseerde regel bereikt wordt
r file lees een bestand, en voeg de inhoud toe aan de uitvoer
s/one/two vervang string “one” door string “two”
w file kopieer de huidige regel naar een ander bestand
= druk het regelnummer af
! command voer het commando uit op de huidige regel
Als je sed gebruikt, kun je aangeven welke regel(s) je wilt bewerken:
$ sed ‘3d’ file
zal de derde regel van het bestand verwijderen
$ sed ‘2,4s/e/#/’ file
Zal de eerste keer dat in de regels 2 tot en met 4 een “e” voorkomt, deze vervangen door “#”
Regels die een string bevatten kunnen geselecteerd worden door reguliere expressies, zoals boven beschreven, te gebruiken. Bijvoorbeeld
$ sed ‘/[Qq]ueen/d’ songs
zal elke regel verwijderen waar het woord “Queeen” of “queen” in voorkomt.
Het is gemakkelijk om lege regels uit een bestand te verwijderen met het gebruik van patronen (patterns)
$ sed ‘/^$/d’ file
dit verwijdert echter geen regels met spaties. Om dat te bereiken, moeten we het patroon een beetje uitbreiden
$ sed ‘/^ *$/d’ file
Waar het “*” teken aanduidt dat het voorgaande teken, ” ” dus de spatie in dit geval, x aantal keer mag voorkomen.
$ sed ‘/InitMenu/a\
> de toe te voegen tekst’ file.txt
Dit voorbeeld zoekt naar de regel waarin de string InitMenu” voorkomt, en voegt erna een nieuwe regel in. Het voorbeeld werkt, als getoond, alleen met een bash of sh shell. Je tikt tot a\, dan druk je op enter (return) en tik je de rest.
Tcsh behandelt nieuwe regels binnen aanhalingstekens op een andere manier. Daarom moet je daar een dubbele backslash gebruiken:
$ sed ‘/InitMenu/a\\
? de toe te voegen tekst’ file.txt
Het ? is het shell teken, net als de > in het bash voorbeeld.
awk
En lest best: awk. De aparte naam komt van de namen van de oorspronkelijke ontwikkelaars: Alfred Aho, Peter Weinberger en Brian Kernighan.
Het awk programma is een van de interessantste onder de UNIX utils. Het is een uitgebreid en complex gereedschap, dat je toestaat via de command line een hele hoop verschillende acties uit te voeren.
Het moet opgemerkt worden dat awk en sed de hoofdmoot vormen van de meer complexe shell scripts. Wat je hiermee kunt doen, zonder C of een andere gecompileerde taal te gebruiken, is dan ook indrukwekkend. De installatie van de Slackware Linux distributie bijvoorbeeld, en veel CGI web programma’s zijn gewone shell scripts.
Tegenwoordig worden de gereedschappen die werken vanaf de commandoregel meestal niet meer uitgevoerd; ze zijn verouderd door de komst van de window omgevingen, en met de komst van PERL zijn veel shell scripts vervangen door perl scripts. Je zou kunnen denken dat de commandoregel opdrachten vergeten zullen worden. Mijn eigen ervaring zegt echter dat veel programma’s kunnen worden uitgevoerd met een paar regels in een shell script (tot een kleine databasemanager toe). Daardoor kun je met de shell erg productief zijn, als je tenminste de commando’s en de shell goed kent.
Als je de kracht van awk en sed combineert kun je dingen snel doen, daar waar je anders een kleine database manager en een spreadsheet voor nodig hebt.
Neem een factuur, waar je de artikelen ziet die je gekocht hebt, hoeveel stuks van elk, en hun prijs per product. We noemen dit bestand “sales”:
oranges 5 250
pears 3 120
apples 2 360
Het is een bestand met 3 velden en tab als het scheidingstekens. Nu wil je een vierde veld definiëren, met daarin de totale prijs per product.
$ awk ‘{total=$2*$3; print $0 , total }’ sales
oranges 6 250 1250
pears 3 120 360
apples 2 360 720
total is de variabele die het product van de waarden die in het tweede en derde veld staan zal bevatten. Na de berekening wordt de hele invoer met de totaalvelden getoond.
awk zelf is bijna een volledige programmeeromgeving, bijzonder geschikt om het werk met informatie uit tekstbestanden te automatiseren. Als je geïnteresseerd bent in deze hulpmiddelen, raad ik je aan ze te bestuderen, met behulp van de man en info pagina’s.
De shell scripts
Shell scripts zijn opeenvolgingen van systeemcommando’s die, na opgeslagen te zijn in een bestand, uitgevoerd kunnen worden.
Shell scripts zijn analoog aan DOS batch files, maar krachtiger. Ze bieden de mogelijkheid om zelf commando’s te maken door bestaande commando’s te combineren.
Uiteraard accepteren shell scripts parameters. Dezen worden opgeslagen in de variabelen $0 (de naam van het commando of script), $1, $2, … tot $9. Alle parameters van de commandoregel kunnen benaderd worden door $*.
Elke teksteditor kan shell scripts genereren. Om een script uit te voeren voer je in:
$ sh shell-script
of, nog beter, je kunt execution rechten geven met
$ chmod 700 shell-script
en het uitvoeren door alleen de naam in te voeren:
$ shell-script
Hier eindigen we het artikel en de discussie over shell scipts, die uitgesteld wordt naar de toekomst. Het volgende artikel zal de meest bekende UNIX tekst editors introduceren: vi & emacs. Elke Linux gebruiker zou die goed moeten kennen.