Ik ga hier niet alle details betreffende Bash-scripts in een sectie van deze HOWTO proberen uit te leggen, slechts 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 er vanuit 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 keer terug als je 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, wat 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 verwezen 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.
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 ronde bij de substituties in de uitdrukking welke het evalueert: als je zegt echo $newvar zal de shell slechts zover gaan dat het vaststelt dat $newvar gelijk is aan de tekststring $testvar, het zal niet evalueren waaraan $testvar gelijk is. eval forceert die evaluatie.
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\' ).
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 allen 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 helder geel 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 te zien krijgen als je een echt lange commandoregel intikt - je zult bemerken dat de shell de tekst op de volgende regel plaatst, voordat het bij de rand van de terminal is belandt, en 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, is omsloten door '\[' en '\]' waarmee aan de shell wordt doorgegeven "alles tussen deze vierkante haken voorafgegaan door een escape-teken, inclusief de vierkante haken zelf, als een niet-afdrukbaar teken te zien." Hetzelfde wordt gedaan met de '\033[0m' waarmee het einde van de kleur wordt aangegeven.
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 handig bij complexe prompts, zodat 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 omdat het ervoor zorgt 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.
Zoals al eerder genoemd, worden de PS1, PS2, PS3, PS4, en PROMPT_COMMAND allen in de Bash-omgeving opgeslagen. Voor degenen onder ons met een DOS-achtergrond, het idee grote hoeveelheden code in de omgeving te gooien, is afschuwelijk, omdat die DOS-omgeving klein was en niet zo goed toenam. Er zijn waarschijnlijk praktische grenzen aan wat je kan en in de omgeving zou moeten plaatsen, maar ik ken ze niet, en we hebben het hier waarschijnlijk over een paar order van grootte groter dan wat DOS-gebruikers zijn gewend. 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 kan schrijven, ik er een shell-functie van maak (in de veronderstelling dat het eenvoudig als een alias kan worden uitgedrukt). Als deze mensen zich druk maken over het geheugen dan is dat met 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 - zie hoe dicht bash bovenaan de lijst staat. Jandorie, het is groter dan sendmail! Zeg hem aan ash of iets dergelijks te komen."
Ik gok het er op dat hij alleen van de console gebruik maakte de dag dat hij dat probeerde: het draaien van X en X apps, 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 er geen zorgen om het te veel te vullen.
Ik riskeer afkeuring van Unix-goeroes wanneer ik dit zeg (voor de veroordeling het al te eenvoudig te maken), maar functies zijn in principe kleine shell-scripts die om efficiëncy-maatregelen in de omgeving worden geladen. Dan weer aanhalend: "Shell functies zijn zo ongeveer zo efficiënt als ze kunnen zijn. Het is het benaderde equivalent van het inlezen van een bewaard bash/bourne Shell-script dat er geen 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 contrast met het uitvoeren van een shell-script: Je shell splitst zich af en het kind doet een exec, eventueel wordt het pad doorzocht, de kernel opent het bestand en bestudeert voldoende bytes om vast te stellen hoe het bestand uit te voeren, 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 het 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 de gewoonte krijgt er op te vertrouwen dat "rm *" je vraagt of je wel zeker bent, kun je belangrijke bestanden op een systeem kwijt raken die geen gebruik maakt van je alias).
Functies worden gebruikt voor complexere programmastructuren. Als een algemene regel, gebruik een alias 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 een algemene regel, je zou je functies relatief klein moeten behouden, 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 wat 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.