4.9. Shell script

Skal man udføre mange kommandoer efter hinanden eller vil man have en anden bruger til at gøre det, kan det med fordel betale sig at lære at skrive shell scripts. I Afsnit 4.5.4 blev vist hvordan man afvikler flere kommandoer efter hinanden på kommando-linjen, men er det mere kompliceret eller der er 20 kommandoer, så vil et shell script med fordel kunne bruges.

I dette kapitel har vi medtaget nogle få almindelige kommandoer. En mere udførlig gennemgang findes på: http://www.tldp.org/HOWTO/Bash-Prog-Intro-HOWTO.html .

Eksempel 4-4. Simpelt kommandofortolkerprogram

Alle kommandoer man kan udføre på kommandolinjen, kan også udføres i et kommandofortolkerprogram (eng. »shell script«). Et kommandofortolkerprogram er blot en tekstfil der starter med at angive hvilken kommandofortolker der skal bruges til at køre det. Resten af tekstfilen indeholder kommandoer til kommandofortolkeren. For at styresystemet skal betragte filen som et program, skal den gøres kørbar (med chmod). Her er der et simpelt eksempel, der bare skriver »Hej med dig.«:

#! /bin/sh
echo Hej med dig.

Hvis de to linjer står i filen "hej", så gør du den kørbar med kommandoen:

[tyge@hven ~]$ chmod +x hej

Når du har gjort filen kørbar, så er den et program og kan køres som enhver anden kommando (næsten):

[tyge@hven ~]$ ./hej
Hej med dig.
./ fortæller din kommandofortolker at det er et program der ligger i det katalog du står i, og ikke et kommandofortolkeren skal lede efter i kommandostien (systemvariablen "PATH").

Hvis du bare udfører kommandoen i filen "hej" direkte på kommandolinjen får du samme resultat:

[tyge@hven ~]$ echo Hej med dig.
Hej med dig.

I dette eksempel valgte vi at bruge "/bin/sh" som fortolker til programmet. Alternativt kunne fortolkeren blandt andet have været /bin/bash, /usr/bin/gnuplot (så ville "echo"-kommandoen dog ikke blive forstået) eller /bin/zsh. Henvisningen til fortolkeren skal skrives på første linje i filen.

Bemærk de første 4 tegn i filen er "#! /". Det er Unix-koden for at der bruges en fortolker til at køre dette program. I dette tilfælde er det så sh der er placeret i kataloget /bin/.

4.9.1. Flere kommandoer

Et af formålene med scripts er at køre flere kommandoer. Har man kun to kommandoer der skal køres, og det kun er dig selv der skal køre dem, kan man blot køre dem efter hinanden på kommando-linjen ved at angive et ; imellem kommandoerne.

[tyge@hven ~]$ echo "Hello, world" ; echo "Hej, verden"
Hello, world
Hej, verden

I et shell-script kan dette så enten skrives som:

#! /bin/sh
echo "Hello, world" ; echo "Hej, verden"

eller med en ny linje imellem hver kommando:

#! /bin/sh
echo "Hello, world"
echo "Hej, verden"

Bemærk at ; blot betyder det samme som en ny linje med en ny kommando. I de følgende eksempler vil ; blive brugt flittigt, men det er egentlig kun for at få scriptet til at se pænere ud og blive mere læseligt.

Hvor ; kan bruges til at skrive flere kommandoer på den samme linje, kan det lige nævnes at \ kan bruges til at dele en meget lang linje op i flere linjer, så den bliver mere læselig. Dette kræver at \ står til sidst på linjen, og der må ikke være et mellemrum eller andet tegn efterfølgende.

#! /bin/sh
echo "Hello, " \
 "world"

I de følgende afsnit vil blive brugt lidt mere besværlige funktioner. Når det scriptet ikke vil som du vil, kan det ofte hjælpe lidt ved at få skrevet ud hvad der sker undervejs. Indsæt set -x for at se hvilke kommandoer der bliver udført.

#! /bin/sh
set -x
echo "Hello, world"
[tyge@hven ~]$ ./hello
+ echo 'Hello, world'
Hello, world

4.9.2. Variable

I shell script kommer man tit ud for at skulle bruge den samme værdi eller tekst flere gange. Her kan man med fordel bruge en variabel til at gemme værdien i. Variabelnavne kan både skrives med majuskler og minuskler, men de fleste skriver kun variabelnavne med majuskler.

#! /bin/sh
VERSION="0.1.2"
echo "Dette er version $VERSION af programmet."
echo $VERSION

Skal der være bogstaver eller tal lige efter variablen, er det nemmest at sætte et par {} omkring. Hvis ikke der sættes {} omkring variablen, vil fortolkeren opfatte den efterfølgende tekst som en del af variablens navn. Alternativt kunne man skrive to linjer, men det kan til tider ikke lade sig gøre.

#! /bin/sh
VERSION="0.1.2"
echo "Dette er version ${VERSION}beta af programmet."

Længden af en streng fås med ${#NAVN}.

#! /bin/sh
echo "\$PATH indeholder ${#PATH} tegn"

Er en variabel ikke nødvendigvis allerede blevet tildelt en værdi, kan man angive en standardværdi (${VARIABELNAVN:-STANDARDVÆRDI}) eller tildele den en standardværdi (${VARIABELNAVN:=STANDARDVÆRDI}):

#! /bin/sh
unset FLOPPY # $FLOPPY har ingen værdi nu.
echo "Data skrives til ${FLOPPY:-/dev/fd0}"
echo "\$FLOPPY er stadig tom: '$FLOPPY'"
echo "Hvis \$FLOPPY er tom, så tilskriv: '${FLOPPY:=/dev/fd0}'"
echo "Nu har \$FLOPPY en værdi: '$FLOPPY'"

Og når scriptet køres:

[tyge@hven ~]$ ./hello
Data skrives til /dev/fd0
$FLOPPY er stadig tom: ''
Hvis $FLOPPY er tom, så tilskriv: '/dev/fd0'
Nu har $FLOPPY en værdi: '/dev/fd0'

Som et mere praktisk eksempel, kan vi skrive et program der enten monterer en diskette fra det første diskettedrev (/dev/df0) eller fra det diskettedrev programmet får på kommandolinjen:

#! /bin/sh
# Et mere praktisk eksempel hvor der enten bruges
# kommando-parameter eller default:
# (Du skal selvfølgelig selv slette 'echo')
echo mount ${1:-/dev/fd0} /mnt/floppy

Vi afprøver det her både med og uden en kommandolinjeparameter:

[tyge@hven ~]$ ./hello
mount /dev/fd0 /mnt/floppy
[tyge@hven ~]$ ./hello /dev/fd1
mount /dev/fd1 /mnt/floppy

Variable kan splittes og manipuleres på forskellige måder med de indbyggede kommandoer. Nogle foretrækker de eksterne programmer såsom cut og sed, men de indbyggede kommandoer kan ofte klare opgaven. Skal man have fat i en del af en tekststreng, kan dette gøres med ${VAR:<start>:<antal>}, første tegn starter ved 0 (nul):

#! /bin/sh
TEKST=abcdef
echo "Skriv kun 'bcd': ${TEKST:1:3}"
echo "Man kan også regne: ${TEKST:1:10-7}"

Skal en del af en variabel udskiftes med noget andet, bruges en substituering. Herunder er det de først forekommende 3 tegn 'bcd' der udskiftes med 1 tegn 'X':

#! /bin/sh
TEKST=abcdefabcdef
echo "Udskift 'bcd' med 'X': ${TEKST/bcd/X}"
[tyge@hven ~]$ ./hello
Udskift 'bcd' med 'X': aXefabcdef

Skal alle forekomster af et eller flere tegn udskiftes bruges »//«:

#! /bin/sh
TEKST=abcdefabcdef
echo "Udskift alle 'bcd' med 'X': ${TEKST//bcd/X}"
[tyge@hven ~]$ ./hello
Udskift alle 'bcd' med 'X': aXefaXef

Den første del af en tekst-streng kan slettes ved brug af '#'. Den tekst der skal fjernes, kan evt. matches ved brug af globbing, på samme måde som man kan liste filer med ls: *?[]. Dette kan for eksempel bruges til at slette den første del af hostname.

#! /bin/sh
echo "Fuldt hostname: ${HOSTNAME}"
echo "Uden maskinnavn: ${HOSTNAME#hven.}"
[tyge@hven ~]$ ./hello
Fuldt hostname: hven.sslug.dk
Uden maskinnavn: sslug.dk

Rigtig smart bliver det ved brug af globbing, hvor man kan fjerne alt frem til det første punktum.

#! /bin/sh
echo "Fuldt hostname: ${HOSTNAME}"
echo "Uden maskinnavn: ${HOSTNAME#*.}"
[tyge@hven ~]$ ./hello
Fuldt hostname: hven.sslug.dk
Uden maskinnavn: sslug.dk

En mere 'grådig' variant kan fjerne frem til den sidste forekomst af punktum. Her er det så '##' der skal bruges.

#! /bin/sh
echo "Fuldt hostname: ${HOSTNAME}"
echo "Uden maskinnavn og domæne: ${HOSTNAME##*.}"
[tyge@hven ~]$ ./hello
Fuldt hostname: hven.sslug.dk
Uden maskinnavn og domæne: dk

På samme måde som den første del af en tekst-streng kan fjernes, kan den sidste del fjernes med %.

#! /bin/sh
echo "Fuldt hostname: ${HOSTNAME}"
echo "Uden TLD: ${HOSTNAME%.*}"
[tyge@hven ~]$ ./hello
Fuldt hostname: hven.sslug.dk
Uden TLD: hven.sslug

Og den mere 'grådige' variant, hvor kun maskinnavn er tilbage ved brug af %%.

#! /bin/sh
echo "Fuldt hostname: ${HOSTNAME}"
echo "Uden TLD: ${HOSTNAME%%.*}"
[tyge@hven ~]$ ./hello
Fuldt hostname: hven.sslug.dk
Kun maskinnavn: hven

Et andet praktisk eksempel hvor den sidste del af tekst-streng skal fjernes, er ved filnavne. Har man en bunke filer der skal konverteres fra fx 'gif' til 'jpg', men beholde det samme filnavn, kan en lille løkke klare det.

#! /bin/sh
for FIL in *.gif; do
        echo "Konverterer ${FIL} til ${FIL%.*}.jpg"
        convert ${FIL} ${FIL%.*}.jpg
done

Og kør scriptet:

[tyge@hven ~]$ ./hello
Konverterer otto.gif til otto.jpg
Konverterer axel.gif til axel.jpg

Skal man splitte en tekststreng der er adskildt af et tegn, kan det gøres på flere måder. Nogle vil nok foretrække den mere behændige kommando cut, men det kan gøres direkte med de indbyggede kommandoer i shell. I det forgående er beskrevet hvordan ${X#*.} kan slette starten af en tekst der matcher #*.. Kommandoen ${X%%.*} sletter den sidste del der matcher %%.*. Hvis disse to regler kombineres lidt, kan tekststrengen splittes ved for eksempel tegnet punktum.

[tyge@hven ~]$ echo "Første del: ${HOSTNAME%%.*}"
Første del: hven
[tyge@hven ~]$ echo "Resten: ${HOSTNAME#*.}"
Resten: sslug.dk

Nu mangler vi så blot at gå ind i en løkke, indtil alle dele adskildt af '.' er skrevet ud. Til det formål skal vi bruge kommandoen while, hvilket vi kommer tilbage til. Ved det sidste element i listen går det galt, for da er der ikke noget punktum tilbage der kan slettes. Derfor kan løkken stoppes når både REST og DEL er ens. Sådan kan det gøres:

#! /bin/sh
REST=$HOSTNAME
DEL=""
while [ "$REST" != "$DEL" ]; do
        echo "Rest: '$REST'"
        DEL=${REST%%.*}  # slet bagfra til sidste forkomne punktum
        REST=${REST#*.}  # slet frem til og med første punktum
        echo "Del : '$DEL'"
done

Og kør scriptet:

[tyge@hven ~]$ ./hello
Rest: 'hven.sslug.dk'
Del : 'hven'
Rest: 'sslug.dk'
Del : 'sslug'
Rest: 'dk'
Del : 'dk'

Hvis du lige fjerne den linje der skriver $REST ud, så er det nok noget nær det du gerne vil have.

Læs mere om streng-manipulering med man bash under "Parameter Expansion".

4.9.3. Parametre til script

De fleste kommandoer eller programmer i UNIX kan startes med parametre på kommando-linjen, og få programmet til at opfører sig lidt anderledes. Parametre nummeres fra 1 og opefter, hvor ${0} er programmet selv.

#! /bin/sh
echo "Dette program hedder: ${0}"
echo "Første parameter: ${1}"
echo "Anden parameter: ${2}"

Programmet kan så afprøves:

[tyge@hven ~]$ ./hello Hello world
Dette program hedder: ./hello
Første parameter: Hello
Anden parameter: world

Skal du vide hvor mange parametere der er på kommando-linjen, ligger denne værdi i den specielle variabel $#.

#! /bin/sh
echo "Antal parametre: $#"
echo "Første parameter: $1"
echo "Anden parameter: $2"

Alle parametre til et script kan skrives ud på flere forskellige måder. Med $* skrives alle parametre ud, adskilt af indholdet af den specielle variabel IFS. Med $@ skrives alle parametre ud som enkeltstående strenge. For at prøve de følgende eksempler, skal der to parametre på når du kalder scriptet, hvoraf det ene skal være en tekst med to ord og " omkring.

#! /bin/sh
echo "Alle parametre: $*"
IFS=","
echo "Alle parametre, nu adskilt af komma: $*"
echo "Alle parametre er adskilt af mellemrum: $@"
[tyge@hven ~]$ ./hello Hej "Linus Torvalds"
Alle parametre: Hej Linus Torvalds
Alle parametre, nu adskilt af komma: Hej,Linus Torvalds
Alle parametre er adskilt af mellemrum: Hej Linus Torvalds

Et program der kører under Linux har tildelt et proces-id – et nummer. Det er de numre som ses med for eksempel ps og pstree -p. Skal man stoppe et program der kører, kan det gøres ved brug af dette proces-id. Proces-id på det script der kører, findes i variablen $$. Denne værdi kan gives videre til et andet program eller script, men i dette eksempel vil vi blot stoppe scriptet selv.

#! /bin/sh
echo "Dette script har proces-id: $$"
echo "Det kan også ses med 'ps' kommandoen:"
ps | grep $$
kill $$

Starter man et program i baggrunden, så man har to programmer kørende samtidigt, kan det være interessant pludseligt at stoppe programmet i baggrunden. Proces-id for baggrundsprogrammer findes i $! variablen.

#! /bin/sh
# Start et program i baggrunden der tager tid
sleep 10 &
echo "'sleep' har proces-id: $!"
echo "Det kan også ses med 'ps' kommandoen:"
ps | grep $!
kill $!

For mere information om de specielle variable, se da man bash og søg efter "Special Parameters" .

4.9.4. if

Betingelser for et program eller et script er næsten uundgåeligt. Den mest brugte kontrol-struktur er if, som i den simpleste form ser således ud, hvor kommandoer der skal udføres er rykket ind for at gøre det mere læseligt:

#! /bin/sh
if true; then
        echo "Værdien er sand"
        echo "Man kan skrive flere linjer her"
fi

Først skal det bemærkes at true ikke er en specielt indbygget kommando i fortolkeren. true er et program der kaldes, og hvis det returnerer 0, er værdien sand. if har selvfølgelig også en else.

#! /bin/sh
if true; then
        echo "Værdien er sand"
else
        echo "Værdien er falsk"
fi

I Afsnit 4.9.1 blev ; nævnt som et tegn der bruges til at adskille kommandoer. Som det ses herover, er der sat et ; ind foran then, hvilket lige så godt kunne stå på næste linje. De fleste der skriver shell-scripts foretrækker dog at skrive det på ovenstående måde, selvom det ved første øjekast ser lidt underligt ud med en ; efter ].

Parametren til if er altså et program der kaldes, og så udføres enten kommandoerne efter then eller efter else.

if bruges altid sammen med et andet program der enten returnerer sand eller falsk. Ofte er det kommandoen [ eller test if bruges sammen med, hvilket er beskrevet i Afsnit 4.9.5.

4.9.5. Test [

Til test for om noget er sandt eller falsk, bruges slet og ret programmet test. I shell-scripts vil man dog ofte ikke skrive test, men derimod bruge det symbolske link [. Når man bruger [ sammen med if, tror mange til at starte med, at [ er en del af kommandoen til if, men det er altså et selvstændigt program. Når man har valgt at lave et symbolsk link til test, skyldes det blot at det er mere læseligt med [. Prøv kommandoen ls -l /usr/bin/[ .

En meget brugt kommando med [, er at undersøge om en fil eksisterer. Dette gøres med parametren '-e'.

#! /bin/sh
if [ -e foo ]; then
        echo "Ja, filen 'foo' findes."
else
        echo "Nej, filen 'foo' findes ikke."
fi

Er det kun hvis filen ikke findes, at der skal gøres noget, bruges not-kommandoen !.

#! /bin/sh
if [ ! -e foo ]; then
        echo "Åh-nej, filen 'foo' findes ikke."
fi

Bemærk at der i ovenstående eksempel er brugt en hel del mellemrum ved if [ ! -e foo ] . Alle disse mellemrum skal være der, ellers kommer der mange underlige fejl der er svære at få øje på. I andre programmeringssprog vil du nok undlade nogle af mellemrumene, men ingen kan undværes i dette eksempel.

test har et væld af muligheder, hvor vi kun viser de mest brugte her. For yderligere information om [ kaldes man test .

#! /bin/sh
if [ -z "$1" ]; then
        echo "Fejl! Første parameter findes ikke (zero)."
fi

if [ ! -z "$1" ]; then
        echo "Første parameter findes, og er: $1"
fi

if [ -n "$1" ]; then
        echo "Første parameter findes (non-zero), og er: $1"
fi

if [ -x foo ]; then
        echo "Programmet 'foo' findes, og du kan køre det."
fi

TEKST="Ja"
if [ "$TEKST" = "Ja" ]; then
        echo "Tekstsvaret var 'Ja'."
fi

NUMMER=3
if [ $NUMMER -eq 3 ]; then
        echo "Heltallet i variablen \$NUMMER er 3"
else
        echo "\$NUMMER er lig med $NUMMER"
fi

if [ $NUMMER -lt 4 ]; then
        echo "\$NUMMER er mindre end 4"
fi

if [ $NUMMER -gt 0 -a $NUMMER -lt 4 ]; then
        echo "\$NUMMER er større end 0 og mindre end 4"
fi

if [ $# -le 2 ]; then
        echo "Der er 2 eller færre parametre på kommando-linjen"
fi

[ kan også bruges i en mere kort form, hvis det kun er en enkelt kommando der efterfølgende skal udføres. Ved at sætte && ind, køres den anden kommando, hvis første kommando var sand. Læs mere om && i Afsnit 4.5.4.

#! /bin/sh
[ -e foo ] && echo "Filen 'foo' findes"

Læs mere om ovenstående parametre til [ i man test, eller man bash under "CONDITIONAL EXPRESSIONS".

4.9.6. Case

Skal man undersøge mange forskellige tekst-strenge, kan det nemt blive uoverskueligt med if-kommandoer. Med case kan man nemt undersøge en hel række og gøre noget forskelligt ved hver situation. Herunder et simpelt eksempel der undersøger hvilket HOSTNAME der er på systemet.

#! /bin/sh
case $HOSTNAME in
        (hven.sslug.dk)
                echo "Vi er på Hven"
                ;;
        (saltholm.sslug.dk)
                echo "Vi er på Saltholm"
                ;;
        (*)
                echo "Vi er et andet sted: $HOSTNAME"
                ;;
esac

Ved kørsel på dit system får du nok et andet svar, fx:

[tyge@hven ~]$ ./hello
Vi er et andet sted: peberholm.sslug.dk

I stedet for at matche teksten helt præcist, kan man bruge globbing som med filnavne:

#! /bin/sh
case $1 in
        (ventilatore)
                echo "Match på 'ventilatore'"
                ;;
        (ventil*)
                echo "Match på 'ventil*': $1"
                ;;
        (ven*)
                echo "Match på 'ven*': $1"
                ;;
        (*)
                echo "Ingen match: $1"
                ;;
esac

Herefter kan forskellige ord så prøves af:

[tyge@hven ~]$ ./hello
Ingen match:
[tyge@hven ~]$ ./hello ventilator
Match på 'ventil*': ventilator
[tyge@hven ~]$ ./hello vender
Match på 'ven*': vender
[tyge@hven ~]$ ./hello world
Ingen match: world

Der er et mere avanceret eksempel med case i Afsnit 4.9.9 der er kombineret med while.

4.9.7. Processubstituering

Man kan komme ud for at skulle bruge output fra et program til et andet program. Dette kan gøres enten ved bruge af `kommando` eller $(kommando). De to måder at gøre det på, udfører det samme. En simpel forklaring på hvornår processubstituering med fordel kan bruges, er hvis man først skal finde en fil og dernæst vil vide hvor stor filen er.

[tyge@hven ~]$ which mount
/bin/mount
[tyge@hven ~]$ ls -ho /bin/mount
-rwsr-xr-x    1 root          67K Feb 26  2002 /bin/mount*

Nu gør vi det samme, men denne gang 'gribes' output fra which, og bruges som parameter til ls.

[tyge@hven ~]$ ls -ho `which mount`
-rwsr-xr-x    1 root          67K Feb 26  2002 /bin/mount*

Og sidder du ved et fremmed tastatur hvor det ikke lige er til at finde tegnet `, så brug $().

[tyge@hven ~]$ ls -ho $(which mount)
-rwsr-xr-x    1 root          67K Feb 26  2002 /bin/mount*

I shell scripts ser man ofte at output fra et andet program bliver gemt i en variabel. Det kunne være et filnavn der er dannet ud fra dags dato.

#! /bin/sh
DATO=$(date -I)
FIL="backup-$DATO.tar.gz"
echo "Dagens backup gemmes i: $FIL"
tar czvf $FIL /home
[tyge@hven ~]$ ./hello
Dagens backup gemmes i: backup-2002-12-31.tar.gz

Mere information om kommando erstatning findes i man bash under Command Substitution.

4.9.8. For-løkker

For-løkker kan udføres på flere måder i shell scripts. Det kan være en liste af ord, liste af filnavne eller et matematisk udtryk. Den simpleste måde er en liste af ord.

#! /bin/sh
for NAVN in Tyge Otto Axel; do
        echo "Han hedder $NAVN"
done
[tyge@hven ~]$ ./hello
Han hedder Tyge
Han hedder Otto
Han hedder Axel

En liste af ord kan også stamme fra et andet program, hvor hvert ord så behandles for sig. Kommandoen date giver som udganspunkt en liste af ord og tal.

#! /bin/sh
date
for TEKST in $(date); do
        echo "Del-streng: $TEKST"
done
[tyge@hven ~]$ ./hello
Tue Dec 31 23:59:59 CET 2002
Del-streng: Tue
Del-streng: Dec
Del-streng: 31
...

For-løkker kan også bruges med fil-navne. Har man en masse filer der skal gøres noget med, skal man stå i katalog hvor filerne er, og så lave en globbing med for eksempel *.html.

#! /bin/sh
for FIL in *.html; do
        echo "Filnavn er: $FIL"
        # Her gøres noget med filen.
        lynx -dump $FIL | lpr -o page-left=36
done

Når der så er filerne index.html og tyge.html bliver resultatet:

[tyge@hven ~]$ ./hello
Filnavn er: index.html
Filnavn er: tyge.html

Er det udvalgt liste af filer der skal gøres noget med, fx kopieres eller installeres, kan man skrive det pænt og organiseret.

#! /bin/sh
# Husk: \ til sidst på linjen, betyder at teksten
# fortsætter på næste linje.
FILER="\
index.html \
tyge.html \
otto.html \
"
for FIL in $FILER; do
        if [ -e $FIL ]; then
                echo "Filnavn er: $FIL"
        fi
done
[tyge@hven ~]$ ./hello
Filnavn er: index.html
Filnavn er: tyge.html
Filnavn er: otto.html

En for-løkke kan selvfølgelig også være en simpel talrække. Herunder udskrives blot tallene 1 til og med 4.

#! /bin/sh
for (( N=1; N<5; N++ )); do
        echo "Tal $N"
done

En anden måde man kan lave lange talrækker er ved brug af kommandoen seq. Herunder udskrives blot tallene 1 til og med 4.

#! /bin/sh
for N in $(seq 1 1 4); do
        echo "Tal $N"
done

En af fordelene ved seq er at tallene kan formateres, for eksempel med nuller foranstillet:

#! /bin/sh
for N in $(seq -f "%04g" 1 1 4); do
        echo "Tal $N"
done

Resultat:

[tyge@hven ~]$ ./hello
0001
0002
0003
0004

4.9.9. Løkke, while

while bruges til at blive ved med at udføre nogle kommandoer, sålænge en betingelse er opfyldt. Et simpelt eksempel der blot skriver det samme hele tiden:

#! /bin/sh
while true; do
        # Skriv sekunder
        date +%S
done

Et mere avanceret eksempel der tager parametre fra kommandolinjen og gør noget forskelligt med hver parameter:

#! /bin/sh
while [ $# -gt 0 ]; do
        case $1 in
                --help)
                        echo "Her er hjælpen"
                        exit 0
                        ;;
                -f)
                        # Næste parameter er filnavn
                        # Det hentes frem med 'shift'
                        shift
                        FIL=$1
                        ;;
                *)
                        echo "Ukendt parameter: $1"
                        exit 1
                        ;;
        esac
        shift
done

Ovenstående eksempel er inspireret af shell-scriptet /sbin/mkbootdisk.

4.9.10. Regne, heltal

Ved brug af dobbelt-paranteser kan regneoperationer udføres direkte, og det er ikke nødvendigt at bruge $ foran variable. Er er nogle eksempler:

#! /bin/sh
(( N=1 ))
echo "Antal filer: $(( N+2 ))"
(( N += 1))
let N += 1

(( N = 5 ))
while (( N -= 1 ))
do
        echo "$N"
done

4.9.11. read, input fra bruger

En nem måde at lave interaktive programmer er ved hjælp af shell-kommandoen read. Med read kan man indlæse input fra brugeren i en variabel, der så efterfølgende kan bruges i scriptet. En simpel måde at bruge read er ved at lave et ophold i et script, hvor brugeren så enten kan vælge at taste Enter eller Ctrl-C.

#! /bin/sh
echo "Tast Enter for at slette filen"
read
rm -f hello.world

read har mulighed for selv at skrive en ledetekst, og så bliver scriptet lige en linje mindre:

#! /bin/sh
read -p "Tast Enter for at slette filen"
rm -f hello.world

read er nok mest brugt til at indlæse input fra brugeren, og det kan gøres så simpelt på en kommandolinje hvor der indlæses til variablen "NAVN":

[tyge@hven ~]$  read -p "Navn: " NAVN
Navn: Tycho
[tyge@hven ~]$  echo $NAVN
Tycho

Ovenstående ser ud på samme måde i et script:

#! /bin/sh
read -p "Navn: " NAVN
echo "Navnet er: $NAVN"

Man kan undlade at angive hvilken variabel der skal indlæses til, og i stedet bruge den som read selv sætter. Navnet på den er REPLY. Scriptet kunne så se således ud:

#! /bin/sh
read -p "Navn: "
echo "Navnet er: $REPLY"

En anden parameter man kan give read er hvor mange tegn der skal indlæses. Det gøres med parametren -n #. Det kunne for eksempel bruges på følgende simple måde:

#! /bin/sh
read -n 1 -p "Tast en vilkårlig tast for at forsætte"

read giver en returkode ved kørsel. Hvis man afslutter indtastning med Enter er returkoden "0" og afslutter man med Ctrl-D er den "1". "0" svarer til "true" og "1" til false, og det kan kombineres med for eksempel while. Se følgende eksempel:

#! /bin/sh
echo "Indtast navne. Tast Ctrl-D for at afslutte."
while read NAVN; do
        echo $NAVN
done

I ovenstående eksempel er det reelt EOF (End-Of-File) der får løkken til at stoppe. Dette kan også bruges hvis input kommer fra et andet program. I eksemplet kommer input fra ls og kanaler så over til read. Det ser nok ud til at det er while der modtager input, men det er altså read der udføre indlæsningen.

#! /bin/sh
ls | while read FILNAVN; do
        echo "Filens navn er: $FILNAVN"
done

read kan også indlæse data fra første linje i en fil.

#! /bin/sh
read FIRSTLINE < foo.bar
echo $FIRSTLINE

Her har nu været gennemgået eksempler som har beskrevet hvordan read kan bruges til de mest almindelige ting. Skal det være mere "poppet" med farver og styring af skærmposition, så kan det gøres med ANSI-koder. For at bruge ANSI-koder skal man sende en ESCAPE til terminalen. Det er vist herunder med ^[, hvilket kan fås i editoren vi ved først at tast Ctrl-V og så Esc.

#! /bin/sh
read -p "^[[41mDit navn: " NAVN
echo "^[[0mNavn er: $NAVN"

Inden du kaster dig ud i at bruge ANSI-koder er det nok klogest at se på programmet dialog som nok kan bruges til mange af de ting du gerne vil.