De shell is de Unix `commandostip', die opdrachten van de gebruikers inleest van het toetsenbord, ze uitvoert en het resultaat op het scherm zet. In de simpelste vorm bestaat een opdracht uit de naam van een programma; de shell zal dat programma dan opstarten, dat de beschikking over het toetsenbord en beeldscherm krijgt, en laat een nieuwe prompt zien als het programma beëindigd is.
Het Unix besturingssysteem wordt wel eens voorgesteld als bestaande uit een harde kern oftewel kernel, die de hardware aanstuurt en hardware aanstuurt en dat de shell een schil erom heen is, die de kernel van de gebruikers afschermt. Het is echter de taak van de kernel om de hardware af te schermen, terwijl de shell dient om toegang tot het systeem te geven. Gebruikersprogramma's hoeven geen gebruik van de shell te maken; de C-bibliotheek heeft bijvoorbeeld wel de functie van een schil om de kernel.
Dit verhaal gaat ervan uit dat u enige ervaring met Unix hebt en dus met het typen van commando's in de shell. Het kan nooit kwaad om de uitgebreide informatie over een besproken commando op te vragen met man commando of (op GNU systemen) info commando.
Er zijn verschillende varianten op het Unix besturingssysteem; alle besturingssystemen die de hier besproken commando's bezitten worden hier voor het gemak met Unix aangeduid, ook al zijn ze niet gebaseerd op de originele code van A T & T. Op een Unix systeem zijn vaak verschillende shells te vinden. Het is ook mogelijk om een shell te draaien op Windows NT™. De voorbeelden bij dit verhaal zijn getest onder Linux en zullen op andere systemen soms aanpassing behoeven.
De oorspronkelijke /bin/sh is de Bourne shell, en de andere zijn daarvan afgeleid. De nummer twee was de C-shell, die handige features had om processen te besturen, maar minder handig om ermee te programmeren, en dus niet verder aan de orde zal komen. De nieuwere Korn shell lijkt weer meer op de Bourne shell, met de features van de C-shell, plus een hele reeks eigen uitbreidingen voor interaktief gebruik en programmeren. De Bourne Again Shell van het GNU projekt heeft eveneens een wijde verbreiding gekregen.
Het bijzondere aan de shell is dat hij zowel een handige commandoverwerker is voor interaktief gebruik als een programmeertaal. Verschillende delen van een Unix besturingssysteem bestaan uit shell scripts, omdat deze gemakkelijk door de gebruiker aan diens behoefte kunnen worden aangepast, wat vooral systeembeheerders waarderen. Shell scripts worden ook vaak gebruikt als een schil om een complexe applicatie.
Het is een kleine moeite om een korte reeks veel gebruikte commando's samen te voegen tot een scriptje: maak met vi (c.q. uw favoriete editor) een file voorbeeld aan met de volgende, vrij willekeurige, inhoud:
Vervolgens typt u chmod ugo+rx voorbeeld waarmee het tekstbestand tot programma wordt gepromoveerd, dat met ./voorbeeld kan worden uitgevoerd. De kernel is zelf in staat te bepalen of een uit te voeren file een script of binair programma bevat.In het voorbeeld is de directory waar het programma zich bevindt expliciet opgegeven. Als die wordt weggelaten zal de shell de lijst van directory's doorzoeken die in de speciale variabele $PATH staat. Het is enigszins riskant om de aktuele directory (.) in het zoekpad op te nemen.
Een standaard vergissing is om eigen programma's de naam test te geven. Helaas is test een ingebouwd commando van de shell. De shell heeft niet zoveel ingebouwde commando's: het meeste werk wordt door externe programma's gedaan.
Als volgende voorbeeld de nederlandstalige versie van het onvermijdelijke nutteloze programma hello, bekend van Kernighan & Ritchie.
Dit lijkt al iets meer op een programma. In de eerste regel staat achter #! aangegeven welk programma het script moet uitvoeren. Normaliter is dat de shell waarmee u inlogt; u kunt hiermee voorkomen dat een programma niet werkt als het bijvoorbeeld vanaf de C-shell wordt gestart. Aan de andere kant zal een script dat begint met #!/bin/bash niet werken op een systeem waar die shell in /usr/local/bin staat of afwezig is. Deze eigenschap is vooral handig voor scripts in andere talen, bijv.: De programmeertaal Perl wordt hier niet verder behandeld. Voor de rest geeft een hekje aan dat de rest van de regel kommentaar is. Een lege commandoregel is toegestaan. Behalve het einde van de regel kan ook een puntkomma worden gebruikt om opdrachten te scheiden die na elkaar worden uitgevoerd.De opdracht echo is een ingebouwde functie van de shell, net als trouwens exit. Echo zorgt ervoor dat de resterende woorden op het standaard uitvoerkanaal (bijv. het scherm) worden afgedrukt. Shell commando's, argumenten en vlaggen moeten atlijd van elkaar worden gescheiden door spaties of andere scheidingstekens.
De opdracht exit beëindigt het script, ook als er nog opdrachten volgen; het gebruik ervan is niet verplicht. Net als de exit functie in een C programma wordt er een foutcode geretourneerd, die 0 bedraagt als er geen fout is opgetreden. Typ maar eens de volgende regels in achter de prompt.
De variabele $? krijgt de exit status van het laatst uitgevoerde commando of script. Na exit of het einde van een script gaat de shell verder met het aanroepende script of vraagt de gebruiker om invoer. True en false zijn externe programma's die niets doen en alleen in shell programma's nut hebben. Het resultaat verschilt nogal van Boolean types in andere programmeertalen.Als u inlogt zal de shell eerst het script /etc/profile uitvoeren, waarin zich algemene instellingen bevinden, gevolgd door .profile in uw eigen directory, waarin u uw persoonlijke instellingen kwijt kunt. Bash en ksh kennen ook initialisatie files ~/.bashrc en ~/.kshrc die iedere keer uitgevoerd worden als een interactieve shell start, bijvoorbeeld bij het openen van een xterm.
De rest van dit hoofdstuk wordt besteed aan Unix en de shell, zonder dat er verder programmeren bij komt kijken. De behandeling zal erg oppervlakkig blijven; in paragraaf enkele Unix tools komen we er uitgebreid op terug.
Het centrale concept in Unix is de file oftewel het bestand. Files worden voortdurend gebruikt waar informatie voor min of meer lange tijd moet worden bewaard. Een file is een reeks bytes, die meestal op een schijf wordt bewaard. De belangrijkste operaties zijn het lezen of schrijven van een aantal bytes. Daarbij wordt de file pointer of bladwijzer om het aantal gelezen of geschreven bytes verplaatst. Verder is het mogelijk om de file pointer naar een opgegeven punt te verplaatsen en de lengte van het bestand in te krimpen tot bijvoorbeeld nul bytes.
De inhoud van een bestand ligt niet vast. Unix maakt vaak gebruik van tekstbestanden. Een byte staat dan voor een letterteken (karakter) van het ASCII alfabet. Het teken <LF> oftewel 0xa oftwel ^J oftewel \n geeft het einde van een regel aan. Er is geen teken nodig om het einde van een bestand te markeren, maar ^D kan worden gebruikt om invoer van de terminal te beëindigen.
Veel tools gaan ervan uit dat een bestand tekst bevat. Bijvoorbeeld zal cat voorbeeld de inhoud van het bestand over het scherm laten lopen. Als het bestand echter geen tekst bevat, dan kan de terminal de kluts kwijt raken van de stuurcodes; doe dus geen /bin/cat /bin/cat. Met cat -v loopt u geen gevaar.
In sommige Unices kunt u de schade repareren met het commando reset. De oude methode is de terminal uit en weer aan te zetten. De schade is te voorkomen. Het kommando file hallo zou iets moeten geven als hallo: Bourne shell script text.
Als file een frase met het woord `executable' erin retourneert, hebben we te maken met een binair programma. Een binair programma kun je alleen uitvoeren: dat werkt wel een stuk sneller dan een ingewikkeld shell script. File kan zich ondanks zijn `magic' uiteraard vergissen.
Verder zijn er speciale files, waarmee bijvoorbeeld apparaten kunnen worden afgelezen of beschreven alsof het files waren; een tape wordt bijvoorbeeld bediend als een hele lange pseudo-file, waarvan de grootte aan een vast maximum is gebonden. Deze worden vaak in de /dev directory gevonden.
Tekst die naar /dev/tty wordt geschreven, wordt wel zichtbaar gemaakt, maar niet opgeslagen. Een poging om /dev/tty uit te lezen retourneert niet de geschreven tekst, maar van het toetsenbord ingevoerde tekens. Unix zal u laten wachten totdat er een regel is ingevoerd, c.q. op de return-toets is gedrukt.
Om een tekstbestand uit te printen zou je cat hallo >/dev/lp0 of iets dergelijks kunnen doen, maar Unix staat dat enkel aan de user root toe. De juiste manier om een bestand te printen is via het printer spoolprogramma met lpr hallo
Een ander bestand dat in scripts gebruikt kan worden is /dev/null, ook wel de bittenbak genoemd, omdat alle data die er naartoe worden geschreven direkt worden weggegooid in tegenstelling tot de vuilnisbak van de Macintosh™. Lezen uit /dev/null is toegestaan, maar er zal niets uit komen.
De directory's kunnen worden beschouwd als een speciaal soort bestand, dat alleen met speciale opdrachten als ls, cp, mv, rm en ln kan worden gemanipuleerd. Directory's bevatten weinig anders als de namen van files en directory's. Daarom is voor een opdracht als rm -f ./voorbeeld schrijfpermissie op de aktuele directory vereist, maar geen permissie op het bestand. In scripts gebruiken we vaak de -f optie, zodat Unix niet nog eens vraagt of we het wel zeker weten.
Een file kan meerdere namen of links hebben; met het commando ln wordt een nieuwe link naar een bestaand bestand gelegd. ln /bin/ls /bin/dir maakt een nieuw commando, dir geheten, dat hetzelfde doet als ls. Met rm wordt het aantal links met één verminderd. Pas wanneer dat tot nul is gedaald en het bestand niet in gebruik is, zal Unix het bestand daadwerkelijk verwijderen.
Het is niet mogelijk naar een link te maken naar een directory of een bestand op een andere schijf. Deze beperking wordt opgeheven door de symbolic link of snelkoppeling, die je maakt met ln -s. Een snelkoppeling loopt het gevaar dat het bestand waar hij naar verwijst niet bestaat of dat een circulaire keten van links ontstaat. Vergelijk de uitvoer van ls -lL eens met ls -l.
We zagen al dat een programma in Unix wordt opgeslagen in een bestand. Een Unix proces is een lopend programma, met zijn data en de context waarin het draait zoals de huidige directory. Moderne besturingssystemen kunnen een proces verdelen in zgn. threads; en programma kan daarmee meerdere taken tegelijk uitvoeren, die hun data delen, wat al snel in een chaos kan ontaarden.
De shell zal, met uitzondering van ingebouwde opdrachten, commando's uitvoeren door er aparte processen voor te starten. Shell scripts worden doorgaans in afzonderlijke processen (subshells) uitgevoerd. Ondanks dat Unix' multi-tasking efficiënter werkt dan huis-, tuin- en keuken besturingssystemen, maakt het voortdurende maken en opruimen van processen shell scripts een stuk langzamer dan andere programmeertalen.
Als een extern programma is opgestart zal de shell gewoon wachten totdat hij een seintje ontvangt van de kernel dat het kindproces is gestorven c.q. beëindigd.
Door een `ampersand' achter een commando te zetten als in voorbeeld&, zal het `in de achtergrond' worden verwerkt, en de shell onmiddellijk om de volgende opdracht vragen.
Een voorbeeld van een ingebouwde opdracht is het commando cd. Beginners verbazen zich er soms over dat een cd opdracht binnen een shell script wel wordt uitgevoerd, maar het effekt ervan vergeten is als het script beëindigd is en terug keert naar de interactieve shell.
Commando's in een script kunnen ook worden uitgevoerd met bijv. . ./voorbeeld. Hiermee worden de opdrachten in het bestand voorbeeld binnen de lopende shell uitgevoerd en werkt cd bijvoorbeeld wel. Een minder gebruikte mogelijkheid is om programma's (niet alleen scripts) te starten met de opdracht exec ervoor. Er wordt dan geen apart proces gemaakt, maar het nieuwe programma wordt in het lopende proces geladen en vervangt het, zodat het oorspronkelijke programma na afloop niet verder kan gaan.
Tot de context van een proces behoren ook de standaard invoer-, uitvoer- en error-kanalen. Voor een interactieve shell verwijst de standaard invoer naar het toetsenbord en de uitvoer en error kanalen naar het beeldscherm. Als u bent ingelogd via een modem of netwerk, dan verwijzen deze kanalen naar uw scherm en toetsenbord in plaats van dat van de computer waarop de shell draait.
Met ./voorbeeld <data >resultaat worden de kanalen omgeleid zodat de inhoud van het bestand data zal worden gelezen en de uitvoer in het bestand resultaat komt. Eventuele foutmeldingen komen nog op het scherm.
De notatie ./voorbeeld <data > >resultaat 2>&1 & zegt dat het script in de achtergrond moet draaien. We zien toch geen resultaten. Het dubbele `groter dan' teken geeft aan dat de uitvoer achter de bestaande inhoud van resultaat moet komen in plaats van het bestand eerst te wissen.
De aanduiding 2>&1 geeft aan dat het standaard error-kanaal (de tweede file-descriptor) een kopie is van nummer 1 (standaard uitvoer), dus foutmeldingen en resultaten verschijnen door elkaar heen in het bestand resultaat; het standaard-invoerkanaal heeft file-descriptor nummer 0. Kind processen erven file-descriptors (open bestanden); de standaard kanalen zijn altijd open. Als de standaard kanalen zijn omgeleid naar bestanden zijn toetsenbord en beeldscherm toch te benaderen door om te leiden naar het pseudo-bestand /dev/tty.
Een here-document is een bijzonder geval van omleiding waarin de te verwerken gegevens in het script-bestand zelf staan.
De rest van het script tot aan woord wordt dan als invoer gebruikt voor het commando (bijv. cat). Het woord dat als markering dient moet letterlijk worden herhaald op een aparte regel. De shell zal wel eventuele substituties uitvoeren op de data.Het volgende commando is een eenvoudige vervanging voor ls.
Het illustreert hoe de shell jokertekens of wildcards behandelt. Als er in een woord een jokerteken voorkomt, dan zal de shell dat woord vervangen door alle bestandsnamen in de huidige directory die overeenkomen met dat zoekpatroon.
De * staat voor iedere reeks van nul of meer tekens, dus ls a*z geeft de lijst van alle bestanden waarvan de naam begint met een a en eindigt op z, zoals `alcatraz' (onder DOS/Windows kunt u de * alleen aan het eind gebruiken). Als er geen corresponderende bestanden zijn dan blijft de asterisk staan, bijv. echo Wie*dit*leest*is*gek
Het is mogelijk te voorkomen dat jokertekens worden vervangen door ze tussen aanhalingstekens te zetten of door er een backslash (\) voor te zetten; ls \* komt van pas, want een bestand met de naam * is niet uitgesloten.
Een . corrrespondeert met een willekeurig teken, zodat de shell p?n kan vervangen door pan pen pin pon. Een reeks van tekens tussen rechte haken kan worden vervangen door één van die tekens, bijv. ls fig-[123456789].jpg door ls fig-1.jpg fig-2.jpg
Let erop dat ls * ook de inhoud van subdirectory's weergeeft. ls geeft alleen files in de huidige directory en ls */ geeft de inhoud van alle subdirectory's.
Twee processen kunnen gemakkelijk via een pijp gekoppeld worden, zodat data die uit de standaard uitvoer van het eerste proces komen, door de standaard invoer van het tweede proces worden verwerkt, bijv.: ps -ef | sort
Het eerste commando geeft een lijst van alle processen die momenteel draaien en het sort commando sorteert ze (op de inlognaam van de gebruiker; werkt helaas niet op alle Unix versies gelijk). Als in een pijp een commando wordt gebruikt dat een filenaam als argument nodig heeft, dan kan meestal `-' worden gebruikt om de standaard invoer aan te duiden; veel commando's gebruiken automatisch de standaard invoer als ze geen bestandsnaam meekrijgen.
Een programma dat data van het standaard invoerkanaal leest en na een eenvoudige bewerking naar het standaard uitvoerkanaal schrijft noemen we een filter. Het eenvoudigste filter is cat, dat data onveranderd kopieert; sort is ingewikkelder.
Een ander filter is dd;. Voor details zie paragraaf split en dd; hier volgt een voorbeeld hoe je een bestand kunt converteren naar hoofdletters: dd conv=ucase <voorbeeld >gesorteerd 2>/dev/null
Dd wordt onder Unix wel gebruikt om een floppy disk te kopiëren met dd if=/dev/fd0 of=image-file Vervolgens verwissel je de disks en kopiëert de image file terug naar floppy.
Vergelijk het resultaat van find /bin | sort eens met ls -1 /bin/* en u ziet dat ls de gewoonte heeft de uitvoer te sorteren. Bij gebruik van een pijp wordt de uitvoer van het eerste proces opgeslagen in een kleine hoeveelheid buffergeheugen. Als dat vol is, wordt de producent stilgezet totdat de consument deze data heeft verwerkt.
Om de uitvoer van een commando op het scherm te bekijken en tegelijk een kopie in een bestand te bewaren gebruikt men iets als ./voorbeeld 2>&1 | tee resultaat
Als u beschikt over het tooltje netcat dan kunt u ook op eenvoudige wijze gegevens over een netwerk versturen. In plaats van prog1 | prog2 start u op machine host netcat -v -l -p 1234 | prog2 en op de andere machine doet u prog1 | netcat host 1234 Hierin is 1234 de gebruikte IP poort: een min of meer willekeurig nummer, mits de betreffende poort nog ongebruikt is, en prog1 en prog2 zijn willekeurige commando's. Netcat fungeert hierin als een pijp tussen processen op verschillende computers.
Alle Unix versies kennen de bovengenoemde anonieme pijp; sommige kennen ook een pijp die als een speciale file in het bestandssysteem voorkomt. Een pijp kan worden aangemaakt met mkfifo pijp of mknod pijp p Het mknod commando wordt ook gebruikt om speciale files van het block of character type aan te maken. Probeer maar eens welke output het file commando op een speciale file geeft.
Volgende | ||
elementair shell programmeren |