|
|
Dit document is beschikbaar in: English Castellano Deutsch Francais Nederlands Portugues Turkce |
door Katja en Guido Socher <katja(at)linuxfocusorg, %20guido(at)linuxfocus.org> Over de auteur: Katja is de Duitse hoofdredactrice van LinuxFocus. Ze houdt van Tux, film & fotografie en de zee. Haar homepage vind je hier. Guido is al lang een Linux-fan omdat het gemaakt wordt door eerlijke en open mensen. Dat is één van de redenen waarom we het open source noemen. Zijn homepage staat op linuxfocus.org/~guido. Vertaald naar het Nederlands door: Floris Lambrechts <floris.lambrechts(at)linuxfocus.org> Inhoud: |
Kort:
In dit artikel leggen we, met veel voorbeelden, uit hoe je kleine shell-scripts kunt schrijven.
#!/bin/shDe tekens #! vertellen het systeem dat de tekst die volgt (in dit geval /bin/sh) een programma is dat de rest van het bestand zal uitvoeren.
chmod +x bestandsnaamJe voert het vervolgens uit door te typen: ./bestandsnaam
varnaam=waardeOm de waarde uit te lezen plaats je een dollarteken voor z'n naam:
#!/bin/sh # assign a value: a="hello world" # now print the content of "a": echo "A is:" echo $aTyp deze tekst in je editor en bewaar het bijvoorbeeld als "eerste". Dan maak je het uivoerbaar (chmod +x eerste in de shell) en voer je het uit (./eerste).
A is: hello worldSoms kun je variabelen verwarren met de rest van de tekst:
num=2 echo "dit is de $numde"Dit geeft niet, zoals je misschien zou willen, "dit is de 2de" maar wel "dit is de " omdat de shell hier gaat zoeken naar een variable met de naam "numde" die nog niet bestaat en dus leeg is. Om duidelijk te maken dat we de variable num bedoelen, zetten we er haakjes rond:
num=2 echo "dit is de ${num}de"Deze keer is het wel goed: als uitvoer krijgen we "dit is de 2de".
Syntax v.h. commando | Doel |
---|---|
echo "een tekst" | schrijft een tekst op het scherm |
ls | geeft een overzicht van de aanwezige bestanden |
wc -l bestand wc -w bestand wc -c bestand |
telt het aantal regels in een bestand of telt het aantal woorden of telt het aantal tekens |
cp bronbestand doelbestand | kopieert het bronbestand naar het doelbestand |
mv oudnaam nieuwnaam | hernoemt of verplaatst het bestand |
rm bestand | verwijdert een bestand |
grep 'patroon' bestand | kijkt of het patroon voorkomt in een bestand Voorbeeld: grep 'zoekdezetekst' bestand.txt |
cut -b kolnum bestand | filtert data uit een bestand met vaste kolombreedte Voorbeeld: toon teken 5 t/m 9 cut -b5-9 bestand.txt Niet te verwarren met "cat", dat iets heel anders doet |
cat bestand.txt | schrijft bestand.txt naar stdout (standard output, het sherm dus) |
file eenbestand | geeft aan welk type bestand eenbestand is |
read var | vraagt de gebruiker om invoer en slaat het op in een variabele (var) |
sort bestand.txt | sorteert lijnen in bestand.txt |
uniq | verwijdert identieke lijnen, in combinatie met sort omdat uniq
enkel opeenvolgende lijnen bekijkt Voorbeeld: sort bestand.txt | uniq |
expr | doet aan wiskunde in de shell Voorbeeld: tel 2 op bij 3 expr 2 "+" 3 |
find | zoekt bestanden Voorbeeld: zoeken op naam: find . -name bestandsnaam -print Dit commando heeft vele mogelijkheden, spijtig genoeg teveel om in dit artikel te bespreken. |
tee | schrijft data naar stdout (het scherm) én naar een bestand Meestal op deze manier: eencommando | tee hetbestand Het schrijft de uitvoer van eencommando naar het scherm en naar hetbestand. |
basename bestand | geeft enkel de bestandsnaam van het gegeven bestand, het verwijdert het directory-pad Voorbeeld: basename /bin/tux geeft gewoon tux |
dirname bestand | geeft enkel de naam van de directory, niet die van het bestand Voorbeeld: dirname /bin/tux geeft enkel /bin |
head bestand | print de eerste regels van het bestand op het scherm |
tail bestand | print de laatste regels van het bestand op het scherm |
sed | in essentie is sed een programma voor het zoeken en vervangen van tekst.
Het leest tekst van de standaard invoer (bijv. uit een pipe) en schrijft het resultaat
naar stdout (meestal het scherm). Het zoekpatroon is een reguliere expressie (meer info in
de referenties onderaan). Dit zoekpatroon mag je niet verwarren met de wildcards van de shell.
Om in een bestand de tekst "linuxfocus" te vervangen door "LinuxFocus" doe je: cat bestand.txt | sed 's/linuxfocus/LinuxFocus/' > nieuwbestand.txt Dit vervangt de eerste "linuxfocus" in een regel door "LinuxFocus". Als de tekst meerdere keren in 1 regel voorkomt: cat bestand.txt | sed 's/linuxfocus/LinuxFocus/g' > nieuwbestand.txt |
awk | awk wordt meestal gebruikt om bepaalde velden uit een tekstregel te lezen.
Standaard worden de velden onderscheiden door een spatie. Een andere
field separator kan je instellen met de optie -F.
cat bestand.txt | awk -F, '{print $1 "," $3 }'Hier gebruiken we de komma (,) om de velden te onderscheiden en printen we het eerste en derde ($1 $3) veld. Als bestand.txt regels heeft zoals Adam Bor, 34, India Kerry Miller, 22, USAdan geeft dit commando: Adam Bor, India Kerry Miller, USAAwk kan nog veel meer maar dit is een veel voorkomend gebruik.@@@ |
grep "hello" bestand.txt | wc -lzoekt alle regels met de tekst "hello" in bestand.txt en telt vervolgens deze regels.
find . -mtime -1 -type f -printzoekt alle bestanden die in de laatste 24 uur veranderd zijn (-mtime -2 zou 48 uur zijn). Als je nu al deze bestanden in een tar-archief wilt opslaan (archief.tar) dan doe je dat zo:
tar xvf archief.tar bestand1 bestand2 ...In plaats van dit allemaal zelf te typen, kun je beide opdrachten (find en tar) combineren met backticks. Tar zal dan alle bestanden archiveren die het find commando gevonden heeft:
#!/bin/sh # De aanhalingstekens zijn backticks (`), geen gewone (') ! tar -zcvf gewijzigd.tar.gz `find . -mtime -1 -type f -print`
if ....; then .... elif ....; then .... else .... fiMeestal gebruik je een speciaal commando (test genaamd) om de if-statements te controleren. Je kunt er stukken tekst (strings) mee vergelijken, testen of een bepaald bestand bestaat, of het leesbaar is, enz.
[ -f "eenbestand" ] : Test of eenbestand wel echt een bestand is. [ -x "/bin/ls" ] : Test of /bin/ls bestaat en of het uitvoerbaar is. [ -n "$var" ] : Test of de variabele $var wel een waarde heeft. [ "$a" = "$b" ] : Test of de variabelen "$a" en "$b" hetzelfde zijn.Doe een keer "man test" en je krijgt een lange lijst met alle soorten testopdrachten voor vergelijkingen en bestanden.
#!/bin/sh if [ "$SHELL" = "/bin/bash" ]; then echo "your login shell is the bash (bourne again shell)" else echo "your login shell is not bash but $SHELL" fiDe variabele $SHELL bevat de naam van de login-shell en dat is wat we testen door het te vergelijken met de string "/bin/bash".
[ -f "/etc/shadow" ] && echo "This computer uses shadow passwords"De && kun je gebruiken als een kort if-statement. De rechterkant wordt enkel uitgevoerd als de linkerkant 'waar' is. Je kunt het bekijken als een AND. In dit voorbeeld: "Het bestand /etc/shadow bestaat AND de opdracht wordt uitgevoerd". De OR operator kun je ook gebruiken, hij wordt geschreven als '||'. Een voorbeeld:
#!/bin/sh mailfolder=/var/spool/mail/james [ -r "$mailfolder" ] || { echo "Can not read $mailfolder" ; exit 1; } echo "$mailfolder has mail from:" grep "^From " $mailfolderHet script test eerst of het de mailfolder wel kan lezen. Zo ja, dan print het al de lijnen die "From" bevatten. Als het de folder niet kan lezen, dan wordt de opdracht die na de OR staat uitgevoerd. Je kunt deze code zo bekijken "De mailfolder moet leesbaar zijn, of anders stoppen we het script". Het probleem is dat er na de OR slechts 1 commando mag staan, terwijl we er twee nodig hebben.
case ... in ...) doe hier iets;; esacWe bekijken een voorbeeld. Het commando
file
gaat na
tot welk type een bepaald bestand hoort:
file lf.gzgeeft:
lf.gz: gzip compressed data, deflated, original filename, last modified: Mon Aug 27 23:09:18 2001, os: UnixWe gaan dit nu gebruiken om een script te schrijven: smartzip. Smartzip kan bzip2, gzip en zip archieven automatisch uitpakken.
#!/bin/sh ftype=`file "$1"` case "$ftype" in "$1: Zip archive"*) unzip "$1" ;; "$1: gzip compressed"*) gunzip "$1" ;; "$1: bzip2 compressed"*) bunzip2 "$1" ;; *) error "File $1 can not be uncompressed with smartzip";; esac
smartzip
articles.zip
bash
en zeer handig voor interactieve toepassingen. De gebruiker kan een keuze maken
uit een lijst van verschillende waardes: select var in ... ; do break done .... nu kun je $var gebruiken ....Een voorbeeld:
#!/bin/sh echo "What is your favourite OS?" select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do break done echo "You have selected $var"Hier is wat dit script doet:
What is your favourite OS? 1) Linux 2) Gnu Hurd 3) Free BSD 4) Other #? 1 You have selected LinuxIn de shell heb je volgende lus-statements:
while ...; do .... doneDe while-lus zal actief blijven zolang de expressie waarop getest wordt 'true' is. Met het sleutelwoord "break" kun je de lus altijd verlaten. Met "continue" sla je het vervolg van de huidige lus over en begin je meteen aan de volgende iteratie.
for var in ....; do .... doneHet volgende zal bijv. de letters A tot C op het scherm printen:
#!/bin/sh for var in A B C ; do echo "var is $var" doneEen meer bruikbaar script, showrpm genaamd, toont een samenvatting van de inhoud van een aantal RPM packages.
#!/bin/sh # list a content summary of a number of RPM packages # USAGE: showrpm rpmfile1 rpmfile2 ... # EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm for rpmpackage in $*; do if [ -r "$rpmpackage" ];then echo "=============== $rpmpackage ==============" rpm -qi -p $rpmpackage else echo "ERROR: cannot read file $rpmpackage" fi doneJe ziet alweer een nieuwe variabele, deze keer $*. Die bevat al de argumenten van de commandoregel. Als je
showrpm openssh.rpm w3m.rpm webgrep.rpm
#!/bin/sh echo *.jpgDit zal worden omgezet naar "mail.jpg tux.jpg".
#!/bin/sh echo "*.jpg" echo '*.jpg'Dit geeft twee keer "*.jpg" op het scherm.
#!/bin/sh echo $SHELL echo "$SHELL" echo '$SHELL'Dit geeft:
/bin/bash /bin/bash $SHELLTenslotte is er nog de mogelijkheid de speciale betekenis van bepaalde karakters op te heffen door er een backslash voor te zetten:
echo \*.jpg echo \$SHELLDit geeft:
*.jpg $SHELLHere-documents
#!/bin/sh # we have less than 3 arguments. Print the help text: if [ $# -lt 3 ] ; then cat <<HELP ren -- renames a number of files using sed regular expressions USAGE: ren 'regexp' 'replacement' files... EXAMPLE: rename all *.HTM files in *.html: ren 'HTM$' 'html' *.HTM HELP exit 0 fi OLD="$1" NEW="$2" # The shift command removes one argument from the list of # command line arguments. shift shift # $* contains now all the files: for file in $*; do if [ -f "$file" ] ; then newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"` if [ -f "$newfile" ]; then echo "ERROR: $newfile exists already" else echo "renaming $file to $newfile ..." mv "$file" "$newfile" fi fi doneDit is het meest complexe script tot nu toe. Het eerste if-statement test of we tenminste drie opties hebben meegegeven (de speciale variabele $# bevat het aantal argumenten). Is dat niet het geval, dan sturen we de helptekst naar het
cat
-commando,
die het op het scherm zet. Nadat de tekst weergegeven is, sluiten we het
script af. Als er daarentegen wél drie of meer argumenten zijn, wijzen
we het eerste toe aan de variabele OLD en het tweede aan de variabele NEW.
Dan 'shiften' we de parameters twee keer om het derde argument in de eerste
positie van $* te krijgen. Met $* beginnen we de for
-lus. Elk van
de argumenten in $* wordt nu één voor één
toegewezen aan de variabele $file. Hier testen we eerst of het bestand bestaat
en dan maken we de nieuwe bestandsnaam door zoeken en vervangen te doen met
sed
. De backticks gebruiken we om het resultaat toe te wijzen
aan de variabele newfile. Nu hebben we alles wat we nodig hebben: de oude
bestandsnaam en de nieuwe. Met de mv
opdracht hernoemen we
vervolgens het bestand.
functienaam() { # binnnen de functie is $1 het eerste argument gegeven aan de functie # $2 het tweede ... code }Je moet functies "declareren" aan het begin van het script voordat je ze kunt gebruiken.
#!/bin/sh # vim: set sw=4 ts=4 et: help() { cat <<HELP xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole USAGE: xtitlebar [-h] "string_for_titelbar" OPTIONS: -h help text EXAMPLE: xtitlebar "cvs" HELP exit 0 } # in case of error or if -h is given we call the function help: [ -z "$1" ] && help [ "$1" = "-h" ] && help # send the escape sequence to change the xterm titelbar: echo -e "\033]0;$1\007" #Het is een goede gewoonte om altijd goede help op te nemen in je scripts. Dat maakt het mogelijk voor anderen (en jezelf) om later het script snel te verstaan en aan te passen.
#!/bin/sh help() { cat <<HELP This is a generic command line parser demo. USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2 HELP exit 0 } while [ -n "$1" ]; do case $1 in -h) help;shift 1;; # function help is called -f) opt_f=1;shift 1;; # variable opt_f is set -l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2 --) shift;break;; # end of options -*) echo "error: no such option $1. -h for help";exit 1;; *) break;; esac done echo "opt_f is $opt_f" echo "opt_l is $opt_l" echo "first arg is $1" echo "2nd arg is $2"Probeer het maar! Je kunt het uitvoeren met bijv.:
cmdparser -l hello -f -- -eenbestand1 eenbestand2Het resultaat is dan
opt_f is 1 opt_l is hello first arg is -eenbestand1 2nd arg is eenbestand2Hoe werkt het? Het gaat alle argumenten af en bekijkt ze met een
case
-
statement. Als het een geldige optie is, wordt die opgeslagen in een variabele
en 'shift' het de commandoregel één positie. De Unix-conventie
is dat opties (dingen die beginnen met een minteken) als eerste komen. Je kunt
duidelijk maken dat het einde van de opties bereikt is door een dubbel
minteken (--) te plaatsen. Dit is bijvoorbeeld nodig als je met egrep
gaat zoeken naar een string met een minteken in: Zoek naar -xx- in bestand f.txt: grep -- -xx- f.txtOnze optie-parser kan ook omgaan met '--' zoals je ziet in de code hierboven.
cp framework.sh mijnscripten begin dan aan het schrijven van je eigen functies.
expr
-commando.
#!/bin/sh # vim: set sw=4 ts=4 et: help() { cat <<HELP b2h -- convert binary to decimal USAGE: b2h [-h] binarynum OPTIONS: -h help text EXAMPLE: b2h 111010 will return 58 HELP exit 0 } error() { # print an error and exit echo "$1" exit 1 } lastchar() { # return the last character of a string in $rval if [ -z "$1" ]; then # empty string rval="" return fi # wc puts some space behind the output this is why we need sed: numofchar=`echo -n "$1" | wc -c | sed 's/ //g' ` # now cut out the last char rval=`echo -n "$1" | cut -b $numofchar` } chop() { # remove the last character in string and return it in $rval if [ -z "$1" ]; then # empty string rval="" return fi # wc puts some space behind the output this is why we need sed: numofchar=`echo -n "$1" | wc -c | sed 's/ //g' ` if [ "$numofchar" = "1" ]; then # only one char in string rval="" return fi numofcharminus1=`expr $numofchar "-" 1` # now cut all but the last char: rval=`echo -n "$1" | cut -b 0-${numofcharminus1}` } while [ -n "$1" ]; do case $1 in -h) help;shift 1;; # function help is called --) shift;break;; # end of options -*) error "error: no such option $1. -h for help";; *) break;; esac done # The main program sum=0 weight=1 # one arg must be given: [ -z "$1" ] && help binnum="$1" binnumorig="$1" while [ -n "$binnum" ]; do lastchar "$binnum" if [ "$rval" = "1" ]; then sum=`expr "$weight" "+" "$sum"` fi # remove the last position in $binnum chop "$binnum" binnum="$rval" weight=`expr "$weight" "*" 2` done echo "binary $binnumorig is decimal $sum" #Het algoritme in dit script bekijkt de decimale waarde van alle cijfers, te beginnen van rechts (1,2,4,8,16,..), en voegt het toe aan de som als het cijfer in kwestie een 1 is. Dus "10" wordt:
lastchar
.
Dit maakt gebruikv an wc -c
om het aantal karakters in een string
te tellen en knipt er dan het laatste uit. De chop
-functie
volgt hetzelfde principe, maar verwijdert eerst het laatste karakter en geeft dan
alle voorgaande weer.
#!/bin/sh # vim: set sw=4 ts=4 et: ver="0.1" help() { cat <<HELP rotatefile -- rotate the file name USAGE: rotatefile [-h] filename OPTIONS: -h help text EXAMPLE: rotatefile out This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1 and create an empty out-file The max number is 10 version $ver HELP exit 0 } error() { echo "$1" exit 1 } while [ -n "$1" ]; do case $1 in -h) help;shift 1;; --) break;; -*) echo "error: no such option $1. -h for help";exit 1;; *) break;; esac done # input check: if [ -z "$1" ] ; then error "ERROR: you must specify a file, use -h for help" fi filen="$1" # rename any .1 , .2 etc file: for n in 9 8 7 6 5 4 3 2 1; do if [ -f "$filen.$n" ]; then p=`expr $n + 1` echo "mv $filen.$n $filen.$p" mv $filen.$n $filen.$p fi done # rename the original file: if [ -f "$filen" ]; then echo "mv $filen $filen.1" mv $filen $filen.1 fi echo touch $filen touch $filenHoe werkt het? Eerst controleren we of de gebruiker een bestandsnaam heeft ingegeven. Dan gaan we in een lus die van 9 aftelt tot 1. Bestand 9 wordt hernoemd tot 10, 8 tot 9 en zo verder. Na deze lus noemen we het originele bestand 1 en maken we een leeg bestand aan met de originele naam.
sh -x foutscriptDit zal het script uitvoeren en alle opdrachten tonen die worden uitgevoerd, samen met de variabelen en de resultaten van wildcards.
sh -n je_scriptAls je geen uitvoer krijgt, dan zijn er geen syntax-fouten gevonden.
|
Site onderhouden door het LinuxFocus editors team
© Katja en Guido Socher, FDL LinuxFocus.org |
Vertaling info:
|
2003-02-03, generated by lfparser version 2.31