Terug naar hoofdinhoud

Linux concept: shell

21 juli 2026

Elke keer dat je een terminal opent, staat er een enkel programma klaar om op te vangen wat je typt. Je schrijft ls, drukt op Enter, en iets moet dat woord lezen, het bijbehorende programma vinden, het uitvoeren, je het resultaat tonen en je daarna om de volgende opdracht vragen. Dat iets is de shell. Het is het programma waar je de hele dag mee praat op een Linux-server, de laag die om de kern van het systeem heen zit, en het is veel meer dan een plek om opdrachten te typen: het is een volwaardige programmeertaal, een opdrachtinterpreter en de lijm die elk klein Unix-gereedschap tot een werkend geheel verbindt. De shell begrijpen is begrijpen hoe je een Linux-machine echt bestuurt.

1. De basis

Een shell is een programma dat de opdrachten leest die je typt, uitzoekt wat je bedoelt en de rest van het systeem vraagt om ze uit te voeren. Het zit tussen jou en de kernel in. Je praat nooit rechtstreeks met de kernel; je praat met de shell, en de shell zorgt ervoor dat programma's namens jou draaien. Als mensen zeggen dat ze "op de command line" of "in een terminal" bezig zijn, is het programma waar ze echt tegen praten een shell.

De duidelijkste manier om het je voor te stellen is een simpele lus die nooit stopt terwijl je werkt. De shell toont een prompt, wacht tot je een regel typt, leest die regel, splitst hem in woorden, beslist wat te doen, voert het uit, toont de uitvoer en toont dan weer de prompt. Dit heet vaak de read-eval-lus, en het is de hele hartslag van interactief Unix:

print a prompt   ->   read a line   ->   understand it   ->   run it   ->   show output
      ^                                                                          |
      +--------------------------------------------------------------------------+

In die lus verschuilen zich twee verschillende taken, en het helpt om ze uit elkaar te houden. Als interactieve opdrachtinterpreter is de shell datgene waar je met de hand in typt. Als scripttaal leest exact hetzelfde programma opdrachten uit een bestand, zodat een lijst opdrachten een programma wordt dat je keer op keer kunt draaien. Het opmerkelijke aan Unix-shells is dat dit geen twee gereedschappen zijn: het is een programma dat hetzelfde werk doet met een andere bron van invoer.

Het juiste mentale model: de shell is een gewoon programma waarvan de hele taak is om andere programma's te draaien. Het is geen onderdeel van de kernel, het is niet "het systeem" - het is een vertaler die de regels die je typt omzet in verzoeken waar de kernel op kan reageren.

Dit artikel gaat over die vertaler: wat hij doet, waar de naam vandaan komt, hoe de shells van vandaag zijn ontstaan uit veertig jaar Unix-geschiedenis, hoe expansie en quoting echt werken, het verschil tussen een builtin en een echt programma, en de verrassende waarheid dat de shell, en niet de opdracht die je aanriep, het meeste slimme werk op je regel doet. De voorbeelden zijn geverifieerd tegen GNU Bash 5.2 op Linux.

1.1 Zien welke shell je draait

Je account heeft een login-shell, de shell die start wanneer je inlogt. De omgevingsvariabele SHELL legt vast welke dat is, en op de meeste Linux-systemen is dat Bash:

$ echo $SHELL
/bin/bash

Die variabele vertelt je je ingestelde shell, maar niet per se de shell die op dit exacte moment draait. Om de shell te vragen die op dit moment echt je opdrachten leest, kijk je naar $0, de naam van het draaiende programma:

$ echo $0
bash

Het verschil is belangrijker dan het lijkt. SHELL wordt eenmalig bij het inloggen ingesteld en verandert nooit; $0 geeft de shell weer waarin je je op dit moment bevindt, en die kan anders zijn als je met de hand een andere shell hebt gestart. Houd beide in gedachten: de een is je instelling, de ander is je werkelijkheid.

1.2 De shell is een bestand zoals elk ander

Een shell is niet magisch en niet ingebouwd in de kernel. Het is een gewoon uitvoerbaar programma dat op schijf staat, en je kunt het opsommen net als elk ander bestand:

$ ls -lh /bin/bash
-rwxr-xr-x 1 root root 1.4M ... /bin/bash

De lijst met shells die je systeem als geldige login-shells beschouwt, staat in een gewoon tekstbestand, /etc/shells:

$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/usr/bin/sh
/bin/bash
/usr/bin/bash
/usr/bin/dash

Elke regel is een programma dat als iemands shell kan dienen. Dat is de eerste hint naar iets waar we aan het eind op terugkomen: de shell is vervangbaar. Niets dwingt je om Bash te gebruiken, en het inruilen voor een ander programma in die lijst verandert het hele gevoel van je command line zonder het systeem eronder aan te raken.

1.3 De terminal is niet de shell

Twee woorden worden voortdurend door elkaar gehaald, en ze uit elkaar houden neemt veel verwarring weg. De terminal is het venster: een applicatie die tekst op je scherm tekent en je toetsaanslagen opvangt. De shell is het programma dat binnenin dat venster draait, die toetsaanslagen leest en je opdrachten uitvoert. De terminal is het glas; de shell is de stem erachter.

LaagWat het isVoorbeelden
Terminal (emulator) De applicatie die tekst toont en je toetsen naar de shell stuurt GNOME Terminal, Konsole, Alacritty, Kitty
Shell Het programma erbinnen dat je opdrachten interpreteert en uitvoert bash, zsh, dash, fish

Het bewijs dat ze los van elkaar staan, is dat je ze vrij kunt combineren. Dezelfde Bash draait probleemloos in GNOME Terminal, Konsole of Alacritty, en elk van die terminals draait net zo makkelijk Zsh in plaats daarvan. Het duidelijkste geval is een login op afstand: als je verbinding maakt via SSH, is er op de andere machine helemaal geen terminal-emulator. Je lokale terminal tekent de tekst, maar de shell waarin je typt draait op de server, met niets dan een netwerkverbinding ertussen. De shell bezit geen venster; hij leest alleen invoer en schrijft uitvoer, en iets anders - een terminal, of SSH - brengt die naar je scherm.

Naar boven

2. Waar de naam vandaan komt

Het woord shell is de tweede helft van een bij elkaar horend paar, en het paar heeft alleen samen betekenis. De kern van een Unix-systeem, het deel dat met de hardware praat, heet de kernel, een Engels woord voor de zachte pit in het midden van een noot. Om die kern heen zit de harde buitenlaag die je daadwerkelijk aanraakt: de shell. De namen komen rechtstreeks uit het beeld van een noot, de kernel binnenin en de shell eromheen.

        you type here
             |
       [ SHELL ]        the outer layer you touch: bash, zsh, sh
       [ kernel ]       the core inside: talks to the hardware
             |
         hardware

De naam is dus een beschrijving van waar het programma zit. Je leeft en werkt in de shell, de buitenlaag. Daaronder, waar het echte werk met de hardware gebeurt, zit de kernel. Elke opdracht die je in de shell typt, wordt uiteindelijk een verzoek dat naar binnen oversteekt naar de kernel en een resultaat dat weer naar buiten komt. De shell is, heel letterlijk, het oppervlak van het systeem.

De naam is oud. Het allereerste programma van dit soort, geschreven voor het vroegste Unix rond 1971, heette al de shell, en het uitvoerbare bestand kreeg de naam sh, kort voor "shell". Die tweeletternaam is al meer dan vijftig jaar meegedragen: op jouw systeem van vandaag duidt sh nog steeds een shell aan, ook al is het programma erachter meerdere keren vervangen.

Naar boven

3. Een korte geschiedenis

De shells die je vandaag gebruikt, zijn de huidige stap in een lange lijn die teruggaat tot het begin van Unix. Die lijn kennen verklaart waarom er meerdere shells zijn, waarom ze bijna maar net niet compatibel zijn, en waarom de standaardshell heet zoals hij heet.

De eerste Unix-shell werd rond 1971 geschreven door Ken Thompson bij Bell Labs. Hij kon opdrachten draaien en hun invoer en uitvoer omleiden, maar het was een klein gereedschap met bijna geen programmeermogelijkheden. Het echte keerpunt kwam in 1979, toen Stephen Bourne, ook bij Bell Labs, een veel krachtigere shell uitbracht met Version 7 Unix. Hij had variabelen, lussen, condities en functies - een echte programmeertaal - en behield de naam sh. Deze Bourne-shell werd het sjabloon waar elke latere shell zich aan afmat.

Rond dezelfde tijd schreef Bill Joy in Berkeley de C-shell (csh) voor BSD Unix, met een syntaxis die dichter bij de taal C lag en de eerste job control en opdrachtgeschiedenis. In 1983 combineerde David Korn bij Bell Labs het beste van beide werelden in de Korn-shell (ksh): de Bourne-taal plus de interactieve mogelijkheden van de C-shell.

Toen kwam de shell die de meeste Linux-gebruikers als eerste tegenkomen. In 1989 schreef Brian Fox Bash voor het GNU-project, als een vrije vervanger van de Bourne-shell. De naam is een woordgrap die je precies vertelt wat het is:

Bash staat voor "Bourne Again SHell" - een vrije herschrijving die "wedergeboren" is uit de Bourne-shell die hij vervangt. De grap is ook een belofte: hij spreekt de Bourne-taal, dus oude scripts blijven werken, terwijl er decennia aan nieuw interactief comfort bovenop komt.

Omdat Bash vrije software was en met GNU meekwam, werd het de standaardshell op bijna elke Linux-distributie, en daarom is het de shell die dit artikel gebruikt. De lijn stopte daar niet. Zsh, in 1990 geschreven door Paul Falstad, dreef de interactieve mogelijkheden het verst door en is nu de standaard op macOS. En dash, een kleine, snelle, strikt minimale shell, wordt op Debian en Ubuntu gebruikt als de gewone sh, omdat scripts die alleen de standaardtaal nodig hebben er sneller in draaien.

JaarShellMijlpaal
1971 Thompson-shell De eerste Unix-shell; draait opdrachten en leidt om, veel meer niet
1979 Bourne-shell (sh) Een echte programmeertaal; het sjabloon voor alles wat volgt
1978 C-shell (csh) C-achtige syntaxis, plus geschiedenis en job control uit Berkeley
1983 Korn-shell (ksh) Bourne-taal gehuwd aan de interactieve mogelijkheden van de C-shell
1989 Bash GNU's vrije "Bourne Again"-shell; de Linux-standaard
1990 Zsh De meest functierijke interactieve shell; vandaag de macOS-standaard

Een standaard bindt deze familie samen. POSIX definieert een gemeenschappelijke shell-taal, gebaseerd op de Bourne- en Korn-shells, die elke serieuze shell kan draaien. Schrijf een script volgens die standaard en het werkt in Bash, dash, ksh en zsh evengoed. Die gedeelde kern is de reden dat een twintig jaar geleden geschreven script nog steeds draait, en waarom sh een betekenisvolle naam is, ongeacht welk programma erachter zit.

Naar boven

4. Eenvoudige toepassingen

Je gebruikt de shell al elke keer dat je een opdracht typt. Maar een paar basisdingen zijn het waard om bewust te doen, omdat ze laten zien wat de shell doet in plaats van de opdracht die je toevallig draait.

4.1 Een opdracht draaien en argumenten meegeven

De eenvoudigst denkbare manier om een shell te gebruiken is een woord typen en op Enter drukken. De shell splitst je regel in woorden gescheiden door spaties: het eerste woord is de opdracht, en de rest zijn de argumenten.

$ ls -l /etc
# word 1: ls        the command to run
# word 2: -l        an option, changing how ls behaves
# word 3: /etc      an argument, telling ls what to list

Dit splitsen is het werk van de shell, niet van de opdracht. Tegen de tijd dat ls start, heeft de shell de regel al in stukken geknipt en als een keurige lijst doorgegeven. Die ene gewoonte - splits de regel, draai het eerste woord, geef de rest door - is de basis waar al het andere op voortbouwt.

4.2 Een andere shell starten en verlaten

Omdat een shell gewoon een programma is, kun je er een starten vanuit een andere. Typ zijn naam en je zit nu in een nieuwe shell; typ exit (of druk op Ctrl-D) en je valt terug naar de shell waar je vandaan kwam:

$ bash          # start a fresh Bash inside your current shell
$ echo $0
bash
$ exit           # leave it and return to where you were

Zo probeer je een andere shell uit zonder je account te wijzigen. Typ dash en je zit in een strikte, minimale shell; typ exit en je bent terug in Bash. Er is niets aan je login veranderd - je hebt simpelweg een programma gedraaid en het daarna verlaten.

4.3 De shell vragen wat een woord betekent

Voordat de shell een woord draait, bepaalt hij wat voor soort ding dat woord is. Je kunt hem dezelfde vraag stellen met de opdracht type (een shell-builtin), die je vertelt of een naam een programma op schijf is, een shell-builtin, een alias of een functie:

$ type ls
ls is /bin/ls
$ type cd
cd is a shell builtin
$ type echo
echo is a shell builtin

Dit is nuttiger dan het in eerste instantie lijkt. Het verklaart waarom cd zich anders gedraagt dan ls, waarom twee opdrachten met dezelfde naam zich in twee shells anders kunnen gedragen, en waar een opdracht eigenlijk vandaan komt. Als iets op de command line je verrast, is type de eerste vraag om te stellen.

Naar boven

5. Gemiddelde toepassingen

Zodra je de shell als interpreter ziet, is de volgende stap om hem je regel te zien herschrijven voordat er een opdracht draait. Tussen het moment dat je op Enter drukt en het moment dat een programma start, voert de shell een reeks transformaties uit die expansies heten. Het meeste van de kracht van de shell, en de meeste van zijn verrassingen, leven hier.

5.1 Expansie: de shell herschrijft je regel

Als je een regel typt, geeft de shell die niet ongewijzigd aan de opdracht door. Hij expandeert eerst verschillende soorten speciale tekst naar hun echte waarden. De belangrijkste zijn:

Je typtDe shell maakt er dit vanNaam
*.txt elke bestandsnaam die eindigt op .txt bestandsnaam-expansie (globbing)
$HOME de waarde van de variabele, bijv. /home/peter variabele-expansie
~ je home-directory tilde-expansie
$(date) de uitvoer van het draaien van date command substitution
{a,b,c} a b c brace-expansie

Hier is het kernpunt dat de meeste mensen missen: de opdracht ziet nooit wat je typte. Hij ziet het resultaat. Als je ls *.txt draait, krijgt het programma ls nooit iets over de * te horen. De shell expandeert *.txt eerst naar de daadwerkelijke lijst met bijpassende bestanden, en ls ontvangt die lijst als gewone argumenten:

$ ls *.txt
# the shell first turns *.txt into: notes.txt report.txt
# then runs:  ls notes.txt report.txt

Dit ene feit verklaart een groot deel van het Unix-gedrag, en we komen erop terug als de grootste verrassing in sectie 7.

5.2 Quoting: expansie uitzetten

Omdat de shell tekens als *, $ en spaties als speciaal behandelt, heb je een manier nodig om hem te zeggen "laat deze tekst met rust". Dat is wat quoting doet. Enkele aanhalingstekens zetten alle expansie uit; dubbele aanhalingstekens zetten het meeste uit maar expanderen nog steeds variabelen:

$ echo $HOME
/home/peter
$ echo "$HOME"        # double quotes: variable still expands
/home/peter
$ echo '$HOME'        # single quotes: taken literally
$HOME

Quoting is geen versiering; het is hoe je de shell bestuurt. De klassieke bug is een bestandsnaam met een spatie erin: zonder aanhalingstekens splitst de shell hem in twee argumenten. Zet een variabele tussen dubbele aanhalingstekens - "$file" - en de hele naam blijft bij elkaar. Dit is verreweg de meest voorkomende oorzaak van kapotte shell-scripts.

5.3 Pipes en omleiding: programma's verbinden

De andere grote kracht van de shell is loodgieterswerk. Elk programma start met drie stromen die al open zijn, en de shell kent ze zowel op nummer als op naam:

NummerNaamStandaard
0 standaardinvoer (stdin) je toetsenbord
1 standaarduitvoer (stdout) je scherm
2 standaardfout (stderr) je scherm

De shell laat je elk van deze stromen omleiden naar een bestand, of de uitvoer van het ene programma verbinden met de invoer van het volgende met een pipe, geschreven als |:

$ ls -l > listing.txt        # send output into a file (> means "write to")
$ ls -l >> listing.txt       # append instead of overwrite
$ sort < names.txt           # take input from a file (< means "read from")
$ ps aux | grep apache       # feed ps output straight into grep

De twee uitvoerstromen zijn met opzet gescheiden, zodat normale resultaten en foutmeldingen naar verschillende plekken kunnen gaan. Je stuurt stderr via zijn nummer, 2, en je kunt het samenvoegen met stdout met de speciale vorm 2>&1, wat betekent "stuur stroom 2 naar waar stroom 1 heen gaat":

$ command 2> errors.txt       # send only error messages to a file
$ command > out.txt 2>&1      # send output AND errors to the same file
$ command 2> /dev/null        # throw error messages away

Dit is het idee dat Unix maakt tot wat het is. Elk gereedschap doet een kleine taak en leest en schrijft platte tekst, en de shell klikt ze aan elkaar tot een pijplijn. De programma's weten niet dat ze verbonden zijn; de shell regelt de stromen zo dat de uitvoer van het ene de invoer van het volgende wordt. Bijna elke serieuze command line die je schrijft is een kleine pijplijn die de shell voor je in elkaar zet.

5.4 Builtins versus externe programma's

Als je een woord typt, kan de shell het op een van twee manieren draaien. De meeste opdrachten, zoals ls of grep, zijn losse programma's die de shell op schijf vindt en start. Maar sommige opdrachten zijn builtins: code die in de shell zelf zit en draait zonder een nieuw programma te starten. Bash heeft er ongeveer zestig:

$ type -a echo        # echo exists as both a builtin and a program
echo is a shell builtin
echo is /usr/bin/echo
$ type cd
cd is a shell builtin

Het onderscheid is niet zomaar weetjes. Sommige opdrachten moeten builtins zijn, omdat ze de shell zelf veranderen - en we zullen in sectie 7 precies zien waarom cd het klassieke voorbeeld is. Onthoud voor nu dat een builtin sneller is (er wordt geen nieuw programma gestart) en dingen kan die een programma van buiten niet kan.

Naar boven

6. Gevorderde toepassingen

De secties hierboven lieten de shell een regel per keer draaien. Hier kijken we hoe hij start, hoe hij zijn instellingen bewaart, hoe hij hele bestanden als programma's draait, en hoe hij meerdere taken tegelijk beheert. Dit is waar losjes gebruik overgaat in echte beheersing van het systeem.

6.1 Login, non-login, interactief, non-interactief

Een shell kan in verschillende modi starten, en de modus bepaalt welke configuratiebestanden hij leest. Twee vragen bepalen de modus. Is het een login-shell (gestart bij het inloggen) of niet? Is het interactief (typen bij een prompt) of niet (een script draaien)?

Hoe hij startteModusLeest (Bash)
Je logt in via SSH of op de console login, interactief /etc/profile, dan ~/.bash_profile
Je opent een nieuw terminaltabblad in een desktop non-login, interactief ~/.bashrc
Je draait een scriptbestand non-login, non-interactief meestal geen

Deze tabel verklaart een hoofdpijn die iedereen uiteindelijk overkomt. Je voegt een instelling toe aan ~/.bashrc, logt in via SSH, en die wordt genegeerd - omdat een login-shell ~/.bash_profile leest, niet ~/.bashrc. De gebruikelijke oplossing is ~/.bash_profile ~/.bashrc te laten laden, zodat beide soorten shells dezelfde opzet krijgen. Als een aanpassing "niet blijft plakken", is de modus van je shell bijna altijd de reden.

6.2 Shell-variabelen versus omgevingsvariabelen

De shell houdt twee soorten variabelen bij, en het verschil bepaalt of de programma's die je start ze kunnen zien. Een gewone shell-variabele bestaat alleen binnen de huidige shell. Een omgevingsvariabele, gemaakt met export, wordt gekopieerd naar elk programma dat de shell start:

$ name=peter                 # a shell variable, private to this shell
$ echo $name
peter
$ bash -c 'echo $name'       # a child shell does NOT see it
                             # (prints an empty line)

$ export name                # now promote it to the environment
$ bash -c 'echo $name'       # the child shell sees it
peter

Dit is precies waarom PATH, HOME en LANG geexporteerd zijn: elk programma heeft ze nodig, dus ze leven in de omgeving, niet slechts in een enkele shell. Het verklaart ook een veelvoorkomende verwarring - een variabele die je in een script instelt, is weg zodra het script eindigt, omdat het script in zijn eigen shell draaide en zijn variabelen meenam.

6.3 Exit-status: hoe opdrachten succes rapporteren

Elke opdracht die klaar is, geeft de shell een getal, zijn exit-status: 0 betekent succes, alles anders betekent een of andere vorm van mislukking. De shell bewaart de laatste in de speciale variabele $?, en dit getal is hoe opdrachten aan elkaar worden geketend met && ("en daarna, als het lukte") en || ("of anders, als het mislukte"):

$ ls /etc > /dev/null
$ echo $?
0
$ ls /nope 2> /dev/null
$ echo $?
2
$ mkdir build && cd build     # only cd if mkdir succeeded

Exit-status is het idee van de shell van waar en onwaar, en het is de ruggengraat van elk script. Een if-statement in de shell test geen waarde; het draait een opdracht en kijkt naar de exit-status ervan. Zodra je ziet dat 0 succes betekent, valt de logica van shell-scripting op zijn plek.

6.4 De shell als programmeertaal: scripts

Zet een lijst shell-opdrachten in een bestand en je hebt een programma. De eerste regel, de shebang, vertelt het systeem welke shell het bestand moet draaien; maak het bestand uitvoerbaar en je kunt het bij naam draaien:

$ cat backup.sh
#!/bin/bash
for f in *.txt; do
    cp "$f" "$f.bak"        # note the quotes: safe with spaces
done

$ chmod +x backup.sh        # make it executable
$ ./backup.sh               # run it like any other command

Niets hier is een aparte "scriptmodus". Dezelfde expansies, quoting-regels, variabelen en exit-statussen die je met de hand gebruikt, zijn de taal waarin het script is geschreven. Dat is de diepe zuinigheid van de Unix-shell: leer hem interactief te gebruiken en je hebt hem al leren programmeren. De shebang-regel kiest simpelweg welke shell het bestand leest, en daarom noemt een zorgvuldig script bewust #!/bin/bash of #!/bin/sh in plaats van te vertrouwen op welke shell er toevallig draait.

6.5 Job control: meerdere dingen tegelijk

Een interactieve shell kan meer dan een programma tegelijk draaien en je ertussen laten wisselen. Eindig een opdracht met & om hem op de achtergrond te starten, druk op Ctrl-Z om de opdracht op de voorgrond te pauzeren, en gebruik jobs, fg en bg om ze te beheren:

$ sleep 300 &            # start a job in the background
[1] 154860
$ jobs                   # list this shell's jobs
[1]+  Running    sleep 300 &
$ fg %1                  # bring job 1 back to the foreground

Dit is een functie van de shell, niet van de programma's. De shell vraagt de kernel om een groep processen aan je terminal gekoppeld te houden en geeft de terminal aan welke taak je ook kiest. Daarom kun je een lange kopieerklus starten, hem naar de achtergrond duwen, en in hetzelfde venster blijven werken - de shell speelt stilletjes voor kleine taakbeheerder van je sessie.

6.6 Een script debuggen

Als een script het verkeerde doet, hoef je niet te gokken. De shell kan je precies laten zien wat hij draait en kan gezegd worden te stoppen zodra er iets misgaat. Je zet deze gedragingen aan met de builtin set, op de command line of bovenaan het script:

OptieKort voorWat het doet
set -x trace (execute) toon elke opdracht, volledig geexpandeerd, vlak voordat hij draait
set -e exit stop het script zodra een opdracht mislukt
set -u unset behandel het gebruik van een niet-ingestelde variabele als een fout
set -o pipefail - laat een pijplijn mislukken als een opdracht erin mislukt, niet alleen de laatste

De nuttigste hiervan om een bug te vinden is set -x, omdat het je de opdrachten na expansie toont - de echte waarden, niet wat je typte. Dat maakt een stil verkeerd resultaat zichtbaar:

$ set -x
$ name=world
$ echo "hello $name"
+ name=world
+ echo 'hello world'
hello world
$ set +x            # turn tracing back off (+ instead of -)

De regels die met + beginnen, zijn de trace van de shell, die laat zien dat $name world werd voordat echo draaide. De drie veiligheidsopties samen - als een regel set -euo pipefail bovenaan een script geschreven - vangen een enorm deel van de echte bugs door te weigeren door te ploeteren na een mislukking of een typefout in een variabelenaam. En nog voordat je een script draait, leest het externe gereedschap shellcheck het en waarschuwt het voor de klassieke fouten, vooral ongequote variabelen:

$ shellcheck backup.sh
In backup.sh line 3:
    cp $f "$f.bak"
       ^-- SC2086: Double quote to prevent globbing and word splitting.

shellcheck draaien op elk script dat je schrijft is de goedkoopste manier om de valkuilen te vermijden waar dit artikel steeds voor waarschuwt. Het kent de regels van de shell tot in detail en wijst de exacte regel en het teken aan waar je er een brak.

Naar boven

7. Iets wat de meeste gebruikers niet weten

7.1 De shell expandeert je regel, niet de opdracht

Dit is verreweg het belangrijkste om te begrijpen over een Unix-shell, en de meeste mensen leren het bij toeval. Als je rm *.log draait, ziet het programma rm nooit de *. De shell expandeert *.log eerst naar de daadwerkelijke lijst met bijpassende bestandsnamen, en geeft die lijst dan aan rm. De opdracht heeft geen idee dat er ooit een wildcard bij betrokken was. Dit verklaart gedrag dat er anders vreemd uitziet: waarom * in een lege directory niets matcht en letterlijk wordt doorgegeven, waarom het quoten van een patroon het stopt met matchen, en waarom je wildcards kunt gebruiken met elke opdracht, zelfs die er niets van weten. De glob is het werk van de shell, gedaan voordat het programma start.

7.2 cd kan geen echt programma zijn

De opdracht die je huidige directory verandert, cd, moet een shell-builtin zijn - het kan onmogelijk een los programma zijn. Hier is waarom. Elk draaiend programma heeft zijn eigen huidige directory, en een programma kan de directory van zijn ouder niet veranderen. Als cd een extern programma was, zou de shell het starten, zou dat nieuwe programma zijn eigen directory veranderen en dan stoppen, en zou je shell precies staan waar hij begon. De wijziging zou verdwijnen met het programma dat hem maakte. De enige manier om de eigen directory van de shell te verplaatsen, is code binnen de shell draaien, en dat is precies wat een builtin is. Dit is de concrete reden waarom sommige opdrachten in de shell moeten leven in plaats van op schijf.

7.3 sh is waarschijnlijk niet de shell die je denkt

Op Debian en Ubuntu is /bin/sh niet Bash. Het is een symbolische link naar dash, een kleinere en striktere shell:

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 ... /bin/sh -> dash

Dit is van belang op het moment dat een script alleen mislukt wanneer het als sh script wordt gedraaid. Bash accepteert stilletjes veel handige uitbreidingen die gewone sh niet accepteert, dus een script met #!/bin/sh dat een Bash-only functie gebruikt, werkt als je het in Bash test en breekt als het systeem het met dash draait. De les is de shebang te laten passen bij de taal die je echt schreef: gebruik #!/bin/bash als je op Bash-functies leunt, en #!/bin/sh alleen voor strikt standaard scripts.

7.4 De shell draait zelf bijna niets

Een interactieve shell is vooral een starter. Als je ls typt, somt de shell de directory niet op; hij vindt het programma ls, maakt een kopie van zichzelf met de system call fork, vervangt die kopie door ls met exec, en wacht tot het klaar is. Het beroemde fork-and-exec-patroon is het hart van hoe Unix programma's start, en de shell doet het voor bijna elke opdracht die je draait. De eigen code van de shell verzorgt de prompt, de expansies, de pipes en de builtins - en stapt dan opzij en laat een los programma het echte werk doen.

7.5 Je kunt je shell volledig vervangen

Omdat een shell gewoon een programma is dat in /etc/shells staat, ben je vrij om de shell die je bij het inloggen krijgt te veranderen. De opdracht chsh ("change shell") doet precies dat, en vanaf dan begroet elke terminal je met de shell die je koos:

$ chsh -s /usr/bin/zsh        # make zsh your login shell
$ echo $SHELL
/usr/bin/zsh

Daarom is de shell echt een buitenlaag en geen vast onderdeel van het systeem. Mensen stappen over van Bash naar Zsh voor de aanvulling, of gebruiken fish voor zijn vriendelijkheid, en niets eronder trekt zich er iets van aan: de kernel, de opdrachten en de bestanden zijn allemaal hetzelfde. Alleen het programma dat je opdrachten leest is veranderd, en dat is precies het hele punt van de shell als shell.

Naar boven

8. Best practices

  • Quote je variabelen. Schrijf "$file", niet $file. Dubbele aanhalingstekens houden een waarde met spaties of speciale tekens als een geheel en voorkomen verreweg de meest voorkomende soort shell-bugs.
  • Weet op welke shell je script mikt. Begin scripts met een bewuste shebang: #!/bin/bash als je Bash-functies gebruikt, #!/bin/sh alleen voor strikt standaard code. Neem niet aan dat sh Bash is.
  • Gebruik type als een opdracht je verrast. Het vertelt je of een naam een builtin, een programma, een alias of een functie is, wat de meeste "waarom deed dat iets anders?"-momenten verklaart.
  • Zet interactieve instellingen in ~/.bashrc, en laad die vanuit ~/.bash_profile. Zo delen zowel login- als non-login-shells dezelfde aliassen, prompt en omgeving.
  • Exporteer variabelen die programma's nodig hebben, houd de rest lokaal. Gebruik export voor dingen als PATH die elk programma moet zien; laat tijdelijke werkwaarden gewone shell-variabelen.
  • Controleer de exit-status in scripts. Keten stappen met &&, test $?, en stop vroeg als een opdracht mislukt in plaats van blindelings de volgende stap op kapotte gegevens te draaien.
  • Kies duidelijkheid boven slimheid. Een shell-oneliner kan uitgroeien tot een onleesbare knoop. Als een pijplijn lang wordt, is een kort, gequoot, becommentarieerd script makkelijker te vertrouwen dan een dichte enkele regel.
  • Lees de documentatie. De shell is grondig gedocumenteerd. man bash is lang maar volledig, help somt de builtins op, en help cd of help type legt er een uit.
$ man bash        # the full Bash manual
$ help            # list all shell builtins
$ help type       # documentation for a single builtin
$ type -a NAME    # every meaning of a command name
Naar boven

9. Veelgemaakte fouten

9.1 Mythe versus werkelijkheid

MytheWerkelijkheid
"De shell is onderdeel van de kern van het besturingssysteem." De shell is een gewoon programma. De kernel is de kern; de shell is een vervangbare laag erbovenop.
"De opdracht die ik draai verwerkt de *-wildcard." De shell expandeert * naar bestandsnamen voordat de opdracht start. De opdracht ziet alleen de afgeronde lijst.
"sh is gewoon een andere naam voor Bash." Op Debian en Ubuntu is /bin/sh dash. Bash-only functies breken als een script onder sh draait.
"Enkele en dubbele aanhalingstekens zijn hetzelfde." Enkele aanhalingstekens zetten alle expansie uit; dubbele expanderen nog steeds variabelen. Het verschil is vaak de bug.
"cd is een programma zoals ls." Het moet een builtin zijn. Een extern programma zou de directory van zijn ouder-shell niet kunnen veranderen.
"Een variabele die ik instel is zichtbaar voor de programma's die ik draai." Alleen als je hem export. Een gewone shell-variabele blijft prive voor de shell die hem instelde.
"Mijn ~/.bashrc draait elke keer dat ik inlog." Een login-shell leest ~/.bash_profile. Als dat bestand ~/.bashrc niet laadt, worden je instellingen overgeslagen.

9.2 Andere valkuilen om te vermijden

  • Variabelen ongequote laten. Een ongequote $file met een spatie erin wordt twee argumenten. Quote hem als "$file" tenzij je een specifieke reden hebt om het niet te doen.
  • Aannemen dat de omgeving van een script blijft bestaan. Variabelen die binnen een script zijn ingesteld verdwijnen als het eindigt, omdat het script in zijn eigen shell draaide. Om je huidige shell te wijzigen, gebruik je source script in plaats van het te draaien.
  • Vertrouwen dat #!/bin/sh Bash betekent. Test scripts met de shell die in de shebang staat, niet alleen interactief in Bash, anders mislukken ze mogelijk pas in productie.
  • Vergeten dat expansie eerst gebeurt. Een gevaarlijke rm * is juist gevaarlijk omdat de shell * naar elk bestand expandeert voordat rm draait. Controleer eerst wat een glob matcht met echo *.
  • $SHELL verwarren met de draaiende shell. $SHELL is je ingestelde login-shell; $0 is de shell die nu draait. Ze verschillen zodra je met de hand een andere shell start.
  • Het verkeerde opstartbestand bewerken. Zet je een instelling in ~/.bashrc maar log je in via SSH en wordt hij genegeerd? Dat is de login-versus-non-login-valkuil uit sectie 6.1.
Naar boven

10. Samenvatting

De shell is het programma waar je op een Linux-machine mee praat: de buitenlaag om de kernel heen, een opdrachtinterpreter en een volwaardige programmeertaal in een. Hij leest je regel, expandeert hem, verbindt programma's tot pijplijnen en vraagt de kernel om ze te draaien. Het is niet het systeem en niet de kern - het is een vervangbare vertaler die omzet wat je typt in wat de machine doet. Zie het zo en de command line houdt op een mysterie te zijn en wordt een gereedschap waarover je kunt redeneren.

  • De shell is een gewoon programma dat opdrachten leest en andere programma's draait; de kernel is de kern, en de shell is de laag die je erbovenop aanraakt.
  • Hij draait een simpele lus: toon een prompt, lees een regel, expandeer en begrijp hem, draai hem, toon de uitvoer, herhaal.
  • Hetzelfde programma is zowel een interactieve interpreter als een scripttaal; leer de een en je hebt de ander geleerd.
  • De naam vormt een paar met kernel uit het beeld van een noot, en de tweeletternaam sh duidt al sinds ongeveer 1971 een shell aan.
  • De shells van vandaag stammen af van de Bourne-shell uit 1979; Bash ("Bourne Again SHell") is de Linux-standaard, met Zsh en dash als veelvoorkomende verwanten, allemaal samengebonden door de POSIX-standaard.
  • Voordat een opdracht draait, voert de shell expansies uit - globbing, variabelen, tilde, command substitution - zodat de opdracht het resultaat ziet, nooit wat je typte.
  • Quoting bestuurt expansie: enkele aanhalingstekens zetten alles uit, dubbele expanderen nog steeds variabelen.
  • Pipes en omleiding laten de shell kleine gereedschappen tot pijplijnen verbinden, wat het kernidee van Unix is.
  • Sommige opdrachten zijn builtins die in de shell leven; cd moet er een zijn, omdat alleen code binnen de shell de eigen directory van de shell kan veranderen.
  • Opstartbestanden hangen af van de login/non-login- en interactief/non-interactief-modus, en daarom lijkt een instelling soms genegeerd te worden.
  • Geexporteerde variabelen bereiken de programma's die je start; gewone shell-variabelen niet. Elke opdracht rapporteert een exit-status, waarbij 0 succes betekent.
  • De shell start programma's met het fork-and-exec-patroon en kan volledig worden vervangen met chsh, omdat het gewoon een programma is dat in /etc/shells staat.

Dit is het spiekbriefje dat het bewaren waard is:

echo $SHELL              your configured login shell
echo $0                  the shell running right now
cat /etc/shells          the shells this system allows
type NAME                is a word a builtin, program, alias, or function
type -a NAME             every meaning of a command name
ls *.txt                 the shell expands *.txt before ls runs
"$var"                   quoted: safe with spaces and special characters
'text'                   single quotes: no expansion at all
cmd1 | cmd2              pipe: cmd1's output into cmd2's input
cmd > file               redirect output into a file (>> to append)
cmd < file               take input from a file
export NAME=value        make a variable visible to launched programs
echo $?                  the exit status of the last command
cmd1 && cmd2             run cmd2 only if cmd1 succeeded
sleep 300 &              run a command in the background
jobs / fg / bg           manage this shell's background jobs
source script            run a script in the current shell
chsh -s /usr/bin/zsh     change your login shell

Een server waarvan de shells en scripts met zorg zijn geschreven - variabelen gequote, shebangs bewust, opstartbestanden begrepen, exit-status gecontroleerd - is een server waarvan de automatisering doet wat je verwacht en luid faalt in plaats van stilletjes. Als je scripts in productie breken maar werken als je ze test, of een cron-taak zich totaal anders gedraagt dan bij je prompt, of niemand precies weet welke shell wat draait, loont het om de laag waar je in typt goed op te zetten en te begrijpen, want het is het oppervlak waardoorheen elk ander deel van het systeem wordt bestuurd.

Naar boven
Linux concept: shell
Peter Martin
Peter Martin
Joomla Specialist

Peter is Joomla specialist en Linux admin voor snelle, veilige en schaalbare websites.