Wat is een script ?
In deze context bedoel ik met script een programma dat bestaat uit leesbare tekst die direct door de computer begrepen en uitgevoerd wordt zonder dat het van tevoren in computernotatie (binary) is omgezet. Zo'n script kan andere programma's uitvoeren, sommetjes maken, dingen op het scherm laten zien, dus eigenlijk alles dat je van een computer zou verwachten.Waarom is een script dan bijzonder ? Vooral omdat het een zeer krachtige manier is van combineren van al bestaande programma's. In het simpelste geval is een script gewoon een lijstje van programma's die na elkaar afgewerkt worden. Complexere scripts kunnen bijvoorbeeld gemaakt worden om een configuratie van programma's met diverse instellingen en afhankelijkheden in een bepaalde volgorde op te starten.
Script | Executable |
---|---|
Wordt uitgevoerd door een host (bv. shell) | Wordt uitgevoerd door het besturingssysteem |
Leesbare 'text' | Machine-specifieke code |
Uitvoering on-the-fly (interpreteren) | Uitvoering na compilatie (vaak sneller) |
Gebruik van reeds bestaande programma's en commando's | Gebruik van libraries |
Een paar voorbeelden van script-talen
- (UNIX) shell script
- PHP
- Javascript
- Python
- Perl
- Actionscript
- Applescript
UNIX shell-scripting
Wat is een UNIX-shell ?
Het UNIX besturingssysteem (a.k.a. operating system of OS) is opgebouwd uit een kernel met daaromheen verschillende lagen die op hun eigen manier en niveau functionaliteit aan die kernel toevoegen. De kernel zorgt dat alle processen op tijd aan de beurt komen en zorgt voor het beheer van het geheugen.Heel dicht tegen de kernel aan bevindt zich de software die externe apparatuur bestuurt, bijvoorbeeld een printer driver, besturingen van filesystemen zoals harddisk, CD en je digitale camera, een netwerk-driver of een driver die je grafische kaart bestuurt zodat je beelden op je monitor ziet.
Op hogere niveaus vind je allerlei processen die onderhoudstaken uitvoeren en pas op de hoogste niveaus, ver van de kernel, vind je de programma's die wij gebruiken. Een shell bevindt zich op dat niveau en geeft ons de mogelijkheid om met het systeem te communiceren.
Opbouw van een UNIX shell script
Hier zijn een paar voorbeeldscripts die je meteen kunt uitproberen.
Download deze file
en pak die uit met de volgende twee commando's:
gzip -d script_examples.tar.gz
tar xvf script_examples.tar
en ga dan naar de directory 'scripts' met 'cd scripts' en kijk met 'ls'
wat erin staat.
Hash
Het eerste character dat je in alle voorbeeldscripts ziet is # ook wel genoemd hash. Dit geeft aan dat de rest van de regel als commentaar beschouwd wordt, dus niet wordt uitgevoerd.HashBang
De combinatie van hash (#) en bang (!) wordt wel HashBang genoemd. Het commando of programma dat hierna op dezelfde regel staat wordt uitgevoerd en krijgt het volledige script weer als input. Op deze manier kun je aangeven met welk programma het script uitgevoerd moet worden.Zoals je ziet wordt het volledige pad (/bin/bash) genoemd, dat moet ook omdat de kernel je PATH-variabele niet gebruikt.
Het mechanisme werkt als volgt:
- je hebt een executable script (chmod 755)
- roep het script aan alsof het een executable is
- de kernel ziet hashbang (#!) en voert programma uit (bv. /bin/bash)
- programma krijgt volledig script als input
- programma moet # als comment zien (vanwege hashbang) en voert de overige regels uit
Twee voorbeeldjes, cat_myself en more_myself:
#! /bin/cat
Dit script laat zichzelf zien
#! /bin/more +2
Dit script laat zichzelf zien, behalve de eerste regel
Een eenvoudig scriptje
#! /bin/bash
#
# Dit script zegt hallo
#
echo "hallo en een prettige dag verder"
Om dit script uit te voeren zet je het in een gewone text file, dus niet Word of zoiets. Laten we dit script "hallo" noemen.
Om het als uitvoerbaar programma te markeren geef je met de file-permissies aan dat het de status van executable krijgt:
chmod 755 hallo
Je kunt het nu uitvoeren door te typen
./hallo
Scriptje met een parameter
#! /bin/bash
#
# Dit script geeft een bericht als je je naam opgeeft als parameter
#
if [ $# -lt 1 ]; then
echo "$0: Hoe heet jij ?"
exit
fi
NAAM=$1
echo
echo "Ik mag gebruiker ${NAAM} feliciteren met deze groet:"
echo
banner Hallo ${NAAM}
Command line parameters
In scripts zie je diverse dollartekens. Die duiden bijna altijd op variabelen of parameters. Parameters (ook wel genoemd "command-line parameters") zijn de argumenten die je aan het script geeft door ze na het script te typen.Het volgende stukje script verwacht drie parameters en laat deze zien. Als je er te weinig geeft krijg je een foutmelding.
#! /bin/bash
if [ $# -lt 3 ]; then
echo "$0: Geef svp drie parameters"
else
echo "Parameter 1: " $1
echo "Parameter 2: " $2
echo "Parameter 3: " $3
fi
De variabele $0 bevat de naam van het script, dus bv. 'hallo'.
$# geeft aan hoeveel parameters de gebruiker heeft opgegeven.
Nog iets nieuws: else. Hiermee geef je een alternatief wanneer de conditie bij de if niet waar is. In dit script betekent dat: als de gebruiker minder dan drie argumenten geeft wordt gevraagd om drie argumenten. Als de gebruiker drie of meer argumenten geeft wordt het stukje code tussen 'else' en 'fi' uitgevoerd.
Parse command line parameters with 'getopts'
http://wiki.bash-hackers.org/howto/getopts_tutorialVariabelen
In het volgende script zie je FILENAME en SAMPLERATE. Dit zijn variabelen die een waarde krijgen. FILENAME krijgt als waarde de naam van de file, maar zonder een eventuele extensie .mid want die wordt er met 'basename' af gehaald. Wat je in de regel met basename verder ziet zijn backticks ( ` ), die voeren het programma dat ertussen staat uit en geven het resultaat terug aan het script. Dit zijn dus geen gewone quotes !!#! /bin/bash
if [ $# -lt 2 ]; then
echo "$0: Geef een file en samplerate"
exit
fi
FILENAME=`basename $1 .wav`
SAMPLERATE=$2
echo "De file ${FILENAME} wordt geconverteerd naar ${SAMPLERATE} Hz"
Alternatief voor backticks
Backticks werken in alle shells, ook oudere types. Een mooi alternatief dat je kunt gebruiken in BASH is $()Met backticks:
echo `basename song.wav .wav`
Met $():
echo $(basename song.wav .wav)
Files creëren, hernoemen en deleten
Het scriptje 'create_some_files' maakt een paar lege files aan:#! /bin/bash
touch a.old b.old c.old d.old
Voer het uit en kijk welke files nu in de directory staan.
Vervolgens voer je het scriptje 'rename_some_files' uit:
#! /bin/bash
for f in [a-c].old; do
newname=`basename $f .old`.new
mv $f $newname
done
en kijk opnieuw welke files er in de directory staan.
Index van songs
wc -l songs/*
./generate_index songs
Dit is het script generate_index:
#! /bin/bash
#
# This script creates an index of song files
#
if [ $# -lt 1 ]; then
echo "$0: Geef een directory met songfiles"
exit
fi
find $1 -type f -exec sed '2,$d' {} \;
sed '2,$d' betekent: delete alle regels vanaf regel 2 t/m het einde van de file. Zo houd je dus alleen de eerste regel over.
Redirection en pipes
Soms wil je de output van een programma of script in een file opslaan, bijvoorbeeld om het later nog eens te kunnen bekijken of om het in een editor te bewerken. Met redirection kun je de output van een programma een andere kant op sturen dan naar de gebruikelijke terminal.Opslaan van het stukje tekst-output uit het eerste scriptje in een file gaat als volgt:
hallo > nieuwefile
Wil je de tekst-output van een programma onderdrukken, bijvoorbeeld omdat er
zoveel tekst over het scherm schiet dat je de output van je eigen script
niet meer herkent dan kun je de in veel gevallen de output van het programma
met "1>" of "2>" naar het UNIX afvoerputje "dev/null" sturen.
Probeer een van de volgende regels:
programma <parameters> 1> /dev/null
programma <parameters> 2> /dev/null
Soms wil je de output van een programma direct in een ander programma gebruiken. Stel dat je een lijst van files in een directory wilt zien, gesorteerd naar aantal regels.
wc -l * | sort -n
of
wc -l * | sort -n | awk 'BEGIN {FS=" "} {print $1 "\t--- " $2}'
In het volgende voorbeeld zie je dat vanaf EOF tot de tweede EOF als invoer wordt gebruikt voor bc. EOF is een zelf bedachte naam. Je mag daar ook iets anders neerzetten, als je het maar consequent doet.
#! /bin/bash
bc << EOF
scale=4
$@
quit
EOF
De kracht van read
#! /bin/bash
ls -1 |
while read name; do
echo $name
say $name
done
Alle regels uit een file regel voor regel bewerken:
cat filename |
while read name; do
echo $name
say $name
done
of zo:
while read name; do
echo $name
say $name
done < filename
De tweede oplosing is soms beter omdat die geen aparte sub-shell start.
Nog een mogelijkheid om zonder 'read' hetzelfde te bereiken:
for f in `ls -1`; do
echo $f
say $f
done
N.B.: say is een OSX programma. Linux gebruikers kunnen
espeak gebruiken.
Maak een textfile "klok" met deze inhoud:
2
5
1
3
Voer dit script uit dat gebruik maakt van de file "klok":
#! /bin/bash cat klok | while read DELAY; do echo "I will sleep for ${DELAY} seconds" say "I will sleep for ${DELAY} seconds" sleep $DELAY done
Test, beslissingen en booleans
Beslissingen met if zijn we al een paar keer tegengekomen. Net als bij andere programmeertalen geeft je bij if een expressie die waar (true) of onwaar (false) is. Als het true is wordt de code na de if uitgevoerd en anders niet.
Je kunt -optioneel- een else gebruiken voor een complementair stuk code.
Maar hoe werkt dat nou precies met true en false? Er zijn meerdere manieren:
1. If met een expressieBASH heeft een nogal cryptische manier om een expressie te testen. Dat doe je door er [ en ] omheen te zetten. Let op: er moeten spaties voor en na [ en ] staan.
TEMPERATURE=-10
if [ $TEMPERATURE -lt -5 ]; then
echo "Elfstedentocht"
else
echo "Zeiltocht"
fi
2. If met een boolean
DEBUG=true
if $DEBUG ; then
echo "Debug"
fi
In beide gevallen kun je het gedrag omkeren met ! bijvoorbeeld zo:
if ! [ $TEMPERATURE -lt -5 ]; then if ! $DEBUG ; then...
Alternatief voor testen met [ ]
Met 'test' kun je dezelfde tests uitvoeren als met [ ] en soms beter leesbaar. Voorbeeld:if test -z $var1 && test -x file; then
echo blah
fi
Rekenen
filenumber=8
echo $filenumber
filenumber=$(( filenumber + 1 ))
printf %04d $filenumber
Alternatief:
filenumber=8
echo $filenumber
(( filenumber = filenumber + 1 ))
printf %04d $filenumber
Loopjes
Hieronder staan twee manieren om een loop te maken.#!/bin/bash
X=0
while [ $X -lt 1200 ]
do
let X=X+120
echo $X
done
#!/bin/bash
for i in `seq 1 10`;
do
echo $i
done
Pas het script zo aan dat het elke halve seconde een nieuw getal laat zien.
Functies
Als je een stukje code hebt dat een duidelijke functie vervult dan kun je er een functie van maken. Je plakt er dan eigenlijk een label op waarmee je het vanuit elke plaats van je script weer kunt aanroepen. Het maken van een functie in Bash doe je zo:my_function() {
LOCALVAR1=$1
LOCALVAR2=$2
<command sequence>
}
Hier zijn de LOCALVARs variabelen die alleen binnen de functie bekend zijn.
De genoemde command sequence is het rijtje instructies dat de body (het
uitvoerende deel) van je functie vormt.
Aanroepen van de functie doe je zo:
GLOBALVAR1="a b c"
GLOBALVAR2=10
my_function $GLOBALVAR1 $GLOBALVAR2
BASH string replacement
BASH substring replacement: ${var//from/into} for all or ${var/from/into} for first one onlyCommando's en programma's die je veel gebruikt bij scripting
Command | Meaning | Examples |
---|---|---|
echo | print string to screen | echo blabla echo "Hello world" |
awk | text processing | awk 'BEGIN {FS=" "} {print $1 $2}' |
sed | stream editor | sed 's/html/xml/g' index.html |
if | conditional execution |
if [ $# -lt 1 ]; then echo "Geef een MIDI-file" exit fiOther comparison operators: -lt < -gt > -le <= -ge >= -eq == -ne != -z length of string is zero =~ matches with a regular expression if $DEBUG ; then echo "Debug" fi |
test expression [ expression ] |
evaluate expression | [ $year -gt 2008 ] [ $year -gt 2008 ] && [ $month == "July" ] # test if argument is not empty or file exists [ ! -z $argument ] # test multiple conditions if ([ $1 == "a" ] || [ $1 == "b" ]) && ( [ $2 == "c" ] || [ $2 == "d" ]); then echo "waar" else echo "onwaar" fi # test if input is a file or a terminal if [ -t 0 ]; then echo script running interactively else echo stdin coming from a pipe or file fi # test if output is a file or a terminal if [ -t 1 ]; then echo output going to the screen else echo output redirected to a file or pipe fi |
else | alternative for conditional excecution |
if [ $1 == "aiff" ]; then echo "Processing AIFF files" else echo "Processing other files" fi |
elif | alternative condition for conditional excecution |
if [ $1 == "aiff" ]; then echo "Processing AIFF files" elif [ $1 == "wav" ]; then echo "Processing WAVE files" else echo "Processing other files" fi |
case | conditional execution with multiple conditions |
case $filesize in [0-9]) echo "Small file" ;; 1[0-9]) echo "Medium size file" ;; 2[0-9]) echo "Large file" ;; *) echo "File too big" ;; esac while read name; do case $name in 5) echo "Nummer 5" ;; "een") echo "Nummer een" ;; "twee") echo "Nummer twee" ;; "einde") exit ;; *) echo "Ken ik niet" ;; esac done |
for | loop construct | for file in *.html ; do echo $file ; done |
basename | strip e.g. extension off a filename | basename beethoven.wav .wav |
${filename#*.} | strip the basename off a filename | More info |
${filename##*.} | keep only the extension of a filename | More info |
sleep | suspend processing for some time | sleep 0.5 |
UNIX scripting: een aantal dingen om op te letten
- Let op: in UNIX zijn hoofdletters andere dingen dan kleine letters.
- Path wordt met forward slashes aangegeven (/home/ikke/mijnfile)
- Als editor kun je bv. gebruiken vim, gvim, macvim, nano of gedit
- Auto completion met de TAB toets
In .bash_profile zet je:
. ~/.bashrc
In .bashrc zet je:
PATH=.:$PATH
Je kunt ook mijn .bashrc downloaden, daar staat het al in. Deze vind je
een niveau hoger in deze website.