Verder Terug Inhoud

3. Bash Programmeren en Shell-Scripts

3.1 Variabelen

Ik ga hier niet alle details betreffende Bash-scripts in een sectie van deze HOWTO proberen uit te leggen, maar alleen de details die betrekking hebben op prompts. Als je meer wilt weten over shell-programmeren en van Bash in het algemeen, beveel ik je van harte het boek Learning the Bash Shell aan, van Cameron Newham en Bill Rosenblatt (O'Reilly, 1998). Vreemd genoeg is mijn kopie van het boek nogal versleten. Nogmaals, ik ga ervan uit dat je al tamelijk wat weet over Bash. Je kunt deze sectie overslaan als je alleen op zoek bent naar de grondbeginselen, maar onthoud het en kom terug als je wat verder bent gekomen.

Variabelen worden in Bash bijna net als in iedere andere programmeertaal toegekend:

testvar=5
foo=zen
bar="bash prompt"

Aanhalingstekens zijn alleen nodig in een toekenning als een spatie (of speciaal teken, hetgeen beknopt wordt besproken) onderdeel uitmaakt van de variabele.

Er wordt iets anders naar variabelen gerefereerd dan hoe ze worden toegekend:

> echo $testvar
5
> echo $foo
zen
> echo ${bar}
bash prompt
> echo $NietToegekend

> 

Er kan naar een variabele worden gerefereerd als $bar of ${bar}. De accolades zijn handig als het onduidelijk is waarnaar zal worden gerefereerd. Als ik $barley schrijf, bedoel ik dan in werkelijkheid ${bar}ley of ${barley}? Merk ook op dat wanneer er naar een waarde wordt gerefereerd die niet is toegekend, er geen fout wordt gegenereerd. In plaats daarvan wordt er niets geretourneerd.

3.2 Aanhalingstekens en Speciale Tekens

Als je speciale tekens in een variabele wilt opnemen, zul je het anders aan moeten halen (quoten):

> newvar=$testvar
> echo $newvar
5
> newvar="$testvar"
> echo $newvar
5
> newvar='$testvar'
> echo $newvar
$testvar
> newvar=\$testvar
> echo $newvar
$testvar
>

Het dollar-teken is niet het enige speciale teken voor de Bash-shell, maar het is een eenvoudig voorbeeld. Een interessante stap die we kunnen nemen om gebruik te maken van de toekenning van een variabelenaam aan een andere variabelenaam is door eval te gebruiken om naar de opgeslagen variabelenaam te verwijzen:

> echo $testvar
5
> echo $newvar
$testvar
> eval echo $newvar
5
> 

Normaal gesproken maakt de shell slechts één substituties-slag in de uitdrukking welke hij evalueert. Als je zegt echo $newvar, zal de shell slechts zover gaan dat het vaststelt dat $newvar gelijk is aan de tekststring $testvar. Hij zal niet evalueren waaraan $testvar gelijk is. eval forceert die evaluatie.

3.3 Commando Substitutie

Ik gebruik in bijna alle situaties in dit document de $(<commando>) conventie voor commando-substitutie: dat wil zeggen,

$(date +%H%M)

betekent "vervang hier de uitvoer van het date +%H%M commando." Dit werkt in Bash 2.0+. In een aantal oudere versies van Bash, van voor 1.14.7, kan het zijn dat je enkele aanhalingstekens openen, (`date +%H%M`) moet gebruiken. Enkele aanhalingstekens openen kunnen in Bash 2.0+ worden gebruikt, maar zullen geleidelijk verdwijnen ten gunste van $(), welke beter is te nesten. Als je een eerdere versie van Bash gebruikt, kun je de aanhalingstekens openen meestal vervangen op die plaatsen waar je $() ziet. Als de commando-substitutie door escape-tekens is omsloten, (d.w.z. \$(commando) ), gebruik dan backslashes om BEIDE aanhalingstekens openen te escapen (d.w.z. \'commando\' ).

3.4 Niet Afdrukbare Tekens in Prompts

Veel van de wijzigingen die kunnen worden aangebracht in Bash-prompts welke in deze HOWTO worden besproken, maken gebruik van niet-afdrukbare tekens. Het wijzigen van de kleur van de prompttekst, het wijzingen van de titelbalk van een Xterm en het verplaatsen van de cursorpositie vereisen alle niet-afdrukbare tekens.

Als je een zeer eenvoudige prompt bestaande uit een groter-dan teken en een spatie wilt:

[giles@nikola giles]$ PS1='> '
> 

Dit is slechts een prompt bestaande uit twee tekens. Als ik het zodanig wijzig dat het groter-dan teken heldergeel is (kleuren worden in een eigen sectie beschreven):

> PS1='\033[1;33m>\033[0m '
> 

Dit werkt prima, totdat je een lange commandoregel intikt. Ook al bestaat de prompt uit nog maar twee afdrukbare tekens (een groter-dan-teken en een spatie), de shell denkt dat deze prompt uit elf tekens bestaat (Ik denk dat het '\033', '[1' en '[0' ieder als één teken telt). Je kunt dit zien als je een echt lange commandoregel intikt. Je zult bemerken dat de shell de tekst op de volgende regel plaatst, voordat het de rand van de terminal bereikt. In de meeste gevallen gaat dit niet goed. Dit komt doordat het voor de shell onduidelijk is hoelang de feitelijke lengte van de prompt is.

Dus gebruik in plaats daarvan:

> PS1='\[\033[1;33m\]>\[\033[0m\] '

Dit is wat complexer, maar het werkt. Commandoregels worden juist afgebroken. De '\033[1;33m' waarmee de kleur geel wordt begonnen, wordt omsloten door teksthaken. Dit, inclusief de teksthaken zelf, is een niet-afdrukbaar teken. Hetzelfde wordt gedaan met de '\033[0m' waarmee het einde van de kleur geel wordt aangegeven.

3.5 Commando's in een Bestand

Als een bestand met commando's wordt aangeroepen (door het op de commandoregel typen van source filename of . filename), worden de regels code in het bestand uitgevoerd alsof ze op de commandoregel werden ingetypt. Dit is vooral nuttig bij complexe prompts, omdat het mogelijk is ze in bestanden op te slaan en de commando's in het bestand aan te roepen door het aanroepen van het bestand.

In voorbeelden zul je vaak aantreffen dat ik aan het begin van bestanden met functies de regel #!/bin/bash heb toegevoegd. Dit is niet noodzakelijk als je een bestand met commando's aanroept, net als dat het niet noodzakelijk is als je een chmod +x toepast op een bestand waaruit de commando's worden gelezen. Ik doe dit om ervoor te zorgen dat Vim (editor van mijn keuze, geen gescheld alsjeblieft, jij gebruikt wat jij wilt) denkt dat ik een shell-script aan het wijzigen ben, en daardoor kleuren syntax highlighting aanzet.

3.6 Functies, Aliassen, en de Omgeving

Zoals al eerder gezegd, worden de PS1, PS2, PS3, PS4, en PROMPT_COMMAND alle in de Bash-omgeving opgeslagen. Voor degenen onder ons met een DOS-achtergrond, is het een afschuwelijk idee om grote hoeveelheden code in de omgeving te plaatsen, omdat die DOS-omgeving klein was en niet precies op de juiste manier toenam. Er zijn waarschijnlijk praktische grenzen aan wat je in de omgeving kan en zou moeten plaatsen, maar ik ken ze niet. We hebben het hier waarschijnlijk over enkele grootte-orden meer dan wat DOS-gebruikers gewend zijn.

Zoals Dan het deed: "In mijn interactieve shell heb ik 62 aliassen en 25 functies. Mijn stelregel is dat als ik iets alleen voor interactief gebruik nodig heb en het eenvoudig in bash te schrijven is, ik er een shell-functie van maak (in de veronderstelling dat het eenvoudig als een alias kan worden uitgedrukt). Als mensen zich druk maken over het geheugen dan is dat bij gebruik van bash niet nodig. Bash is één van de grootste programma's die ik op mijn linux box draai (buiten Oracle). Draai top zo nu en dan en druk op 'M' om op geheugen te sorteren en zie hoe dicht bash bovenaan de lijst staat. Jandorie, het is groter dan sendmail! Zeg hun om ash of iets dergelijks aan te schaffen."

Ik gok erop dat hij alleen van de console gebruikmaakte toen hij het draaien van X en X-apps probeerde. Ik heb heel wat liggen dat groter is dan Bash. Maar het idee is hetzelfde: de omgeving is iets om te gebruiken, en maak je geen zorgen als het te veel wordt.

Ik riskeer afkeuring van Unix-goeroes wanneer ik dit zeg (voor de misdaad het al te eenvoudig te maken), maar functies zijn in principe kleine shell-scripts die om efficiëncy-redenen in de omgeving worden geladen. Dan weer aanhalend: "Shell functies zijn ongeveer zo efficiënt als ze kunnen zijn. Het is bij benadering equivalent aan het inlezen van een bewaarde bash/bourne Shell-script zonder dat bestands-I/O nodig is, aangezien de functie zich al in het geheugen bevindt. De shell-functies worden typisch geladen vanuit [.bashrc of .bash_profile] afhankelijk van of je ze alleen in de initiële shell wilt of ook in de subshells. Dit in tegenstelling tot het uitvoeren van een shell-script. Je shell splitst zich af en de child doet een exec. Eventueel wordt het pad doorzocht. De kernel opent het bestand en onderzoekt voldoende bytes om vast te stellen hoe het bestand uitgevoerd moet worden. In het geval van een shell-script moet er een shell worden gestart met de naam van het script als argument. De shell opent vervolgens het bestand, lees het in en voert de opdrachten uit. Vergeleken met een shell-functie, kan al het andere dan het uitvoeren van de opdrachten worden aangemerkt als onnodige overhead."

Aliassen zijn simpel aan te maken:

alias d="ls --color=tty --classify"
alias v="d --format=long"
alias rm="rm -i"

Alle argumenten die je aan de alias doorgeeft worden aan de commandoregel van het ge-alias-te commando doorgegeven (ls in de eerste twee gevallen). Merk op dat aliassen kunnen worden genest en ze kunnen worden gebruikt om een gewoon unix-commando zich op een andere manier te laten gedragen. (Ik ben het met het argument eens dat je de laatste soort aliassen niet zou moeten gebruiken.Als je in de gewoonte vervalt er op te vertrouwen dat "rm *" je vraagt of je wel zeker bent, kun je belangrijke bestanden op een systeem kwijtraken die geen gebruik maakt van je alias).

Functies worden gebruikt voor complexere programmastructuren. Als algemene regel moet je een alias gebruiken voor alles dat in één regel kan worden gedaan. Functies verschillen van shell-scripts in die zin dat ze in de omgeving worden geladen zodat ze sneller werken. Nogmaals, als algemene regel zou je je functies relatief klein moeten houden, en ieder shell-script dat relatief groot wordt zou een shell-script moeten blijven in plaats dat je het omzet in een functie. Je beslissing om iets als een functie te laden zal ook afhangen van hoe vaak je het gebruikt. Als je een klein shell-script niet vaak gebruikt, laat het dan als een shell-script. Als je het vaak gebruikt, zet het dan om in een functie.

Om het gedrag van ls te wijzigen, zou je iets kunnen doen als het volgende:

function lf
{ 
    ls --color=tty --classify $*
    echo "$(ls -l $* | wc -l) files"
}

Dit zou makkelijk als een alias kunnen worden ingesteld, maar ter wille van het voorbeeld zullen we er een functie van maken. Als je de tekst typt in een tekstbestand en je past een source toe op dat bestand, zal de functie in je omgeving staan en onmiddellijk beschikbaar zijn op de commandoregel zonder de overhead van een shell-script zoals voorheen werd aangegeven. Het nut hiervan wordt duidelijker als je overweegt meer functionaliteit aan de functie van hierboven toe te voegen, zoals het gebruik van een if-opdracht om speciale code uit te voeren als er links in de listing worden gevonden.


Verder Terug Inhoud