1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
|
\chapter{Shell Scripting}
\label{scripting}
Er is niets hinderlijker dan keer op keer lange opdrachten intikken.
Shellscripts zijn eenvoudige tekstbestanden met shellopdrachten die
het tekstbestand omzetten in een comfortabel ``hulpmiddel'' welke
eenvoudiger in het gebruik is dan het keer op keer weer intikken van
die opdrachten.
Nu we genoeg weten over de werking van de shell, moeten we onze
aandacht gaan richten om het maken van permanente wijzigingen. Het is van belang
eenvoudig te gebruiken omgevingen te hebben om complexe taken te
verrichten, omdat we anders blijven hangen in het dagelijks herhalen van
talrijke kleine opdrachten. Het vanzelfsprekende aanvangspunt
is het aanpassen van \file{.bashrc}
en \file{.cshrc} bestanden met een paar aliassen en \"e\"en of
twee prompts.
\section{Initialisatiebestanden van de shell}
We gaan eenvoudige aliassen en shellvariabelen ontwikkelen voor onze
bashshell dat het leven er een stuk eenvoudiger op zal maken wanneer
je op het Linux systeem inlogt.
\subsection{Initialisatiebestanden voor Bash}
Begin nu met het wijzigen van \file{.bashrc}.
Schrijf hierin de volgende aliassen en promptdefinities:
\begin{alltt}
alias m='less -EMsX '
alias h='history | tail -40'
alias d='ls -l \$* | less -EMsX'
alias dt='ls -blt \$* |less -EMsX'
alias Dt='ls -aglt \$* |less -EMsX'
PS1='[\bs{s}]:\bs{u}: '
\end{alltt}
De \term{\$*} staat voor alle resterende variabelen op de opdrachtregel
afgezien van de aanroepende opdracht. Het is gelijk aan alle
opeenvolgende opdrachtregelvariabelen, \term{``\$1 \$2 ...''}.
Nadat je \file{.bashrc} hebt aangepast, moet je een ``\program{source .bashrc}"
uitvoeren om je wijzigingen te initialiseren.
Typ \program{alias} om te bekijken wat de definities van het systeem zijn en
probeer je nieuwe aliassen uit.
\newpage
Denk eraan dat we ook een \file{.bash\_profile} hebben waarvan wordt
verondersteld dat het je omgevingsvariabelen bevat:
\begin{Verbatim}[fontfamily=courier,frame=single,commandchars=\\\{\}]
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# Gebruikersspecifieke omgeving en opstartprogramma's
PATH=\$PATH:/usr/local/bin:\$HOME/bin
ENV=\$HOME/.bashrc
USERNAME=""
export USERNAME ENV PATH
\end{Verbatim}
%$
Let op de laatste export opdracht aan het einde van het bestand.
\subsection{Initialisatiebestanden voor Tcsh}
Laten we nu de analoge aliassen in \file{.cshrc} aanmaken:
\begin{alltt}
alias lester "less -EsX" # Gebruik less als onze pager..
alias h 'history \bs!* | tail -40 | lester'
alias m lester # Gebruik om meer voor te stellen,
# nu is meer less.
alias d 'ls -l \bs!* | lester'
alias dt 'ls -lt \bs!* | lester' # Sorteer in chronologische volgorde
alias Dt 'ls -laFt \bs!* | lester' # Zelde als hierboven, toon dot (.xxx)
# bestanden
set prompt="\%n:\%~: " # "user:/location/location/location: "
\end{alltt}
Probeer deze onder tcsh uit na uitvoering
\tcsh{source .cshrc}
Activeer deze wijzigingen door het intikken van \program{source .cshrc}.
Typ \program{alias} om de systeemdefinities te bekijken en probeer er
een aantal van uit. Zowel \program{Tcsh} als \program{Csh} gebruiken
\file{.cshrc} en \file{.login}. Het bestand \file{.login} wordt
normaal gesproken gebruikt om omgevingsvariabelen en informatie
over het \term{pad} in op te slaan, terwijl \file{.cshrc} normaal
gesproken wordt gebruikt voor het bewaren van aliassen en informatie
over de prompt.
%----------------------------------------------------------------------
%----------------------------------------------------------------------
\newpage
\section{Utility Shell Scripts}
Ons eerste doel is een scriptutility aan te maken dat bestanden in een
directory met de naam \file{/home/\user/address} af zal drukken.
De syntax zal zijn:
\command{phone.shell}\option{ bestand1 bestand2 ....}
Vanwege de simpele en duidelijke syntax van \program{tcsh},
zullen we eerst de \program{tcsh} taal gebruiken om een oplossing te
construeren. Daarna laten we de equivalente \program{bash} oplossing zien.
\subsection{Tcsh oplossing}
Het eerste wat we willen doen is in het script aangeven dat het een
\program{csh} script is, en wat het acceptabele aantal parameters zijn.
Begin door het wijzigen van een bestand met de naam
\file{$\tilde$/bin/phone.csh}:
\begin{alltt}
#! /bin/csh -f # De eerste ``#'' hier, is geen commentaar.
#
set a=\$0 # \$0 refereert naar de naam van dit script.
if(\$#argv < 1 ) then
echo Usage: \$a:t 'Name'
exit(1)
endif
\end{alltt}
De term \term{set a=\$0} refereert naar de $0^{de}$ component van de
opdrachtregel, wat het opdrachtbestand \program{phone.csh} is in ons geval.
De \term{\$a:t} instrueert \program{csh} alle voorafgaande componenten
van de padnaam te verwijderen, aangezien we niet ge\"interesseerd zijn
in het zien van het volledige pad. Test dit door het aanroepen van de
naam van het script:
\begin{alltt}
\bash{phone.csh}
Usage: phone.csh Name
\end{alltt}
Dacht je eraan \command{chmod} op het script toe te passen zodat het
uitvoerbaar is?
Tot dusverre doet het script niets, behalve dat het ons vertelt of we het
juiste formaat hebben. Vervolgens zullen we een \term{foreach} regel
toevoegen dat ons controle zal geven voor elke bestandsnaam:
\begin{alltt}
foreach name (\$*)
echo ~/address/\$name
end
\end{alltt}
\newpage
Als het goed is zal je script nu iedere bestandsnaam die je invoert op
de opdrachtregel afdrukken als een controle in onze vooruitgang.
Test nogmaals dit script en controleer of de lus werkt:
\begin{alltt}
\bash{phone.csh red green blue}
/home/\user/address/red
/home/\user/address/green
/home/\user/address/blue
\end{alltt}
Nu we weten dat het werkt, vervangen we de \program{echo} opdracht door
de correcte \program{cat} phrase.
Nu we er toch mee bezig zijn, zullen we controleren op het bestaan van
elk bestand met de ( -e filename ) test.
De vorige \term{foreach} clausule zou er nu ongeveer zo uit moeten zien:
\begin{alltt}
foreach name (\$*)
if (-e ~/address/\$name) then # De (-e) vlag controleert op het bestaan.
cat ~/address/\$name
else
echo ``no such file \$name ''
endif
echo ``-------------------------'' # Scheid regels door een lijn.
end
\end{alltt}
Het uiteindelijke script zou hier op moeten lijken:
\begin{verbatim}
#! /bin/csh -f # Vertel het systeem dat dit een tcsh script
# phone.csh # Geef als commentaar de bestandsnaam.
set a=$0 # $0 refereert naar de naam van dit script.
if($#argv < 1 ) then # Als juiste aantal argumenten,
echo Usage: $a:t 'name' # Echo de juiste syntax
exit(1) # Exit met foutcode 1
endif # End if
#
foreach name ($*) # Voor elke naam op de opdrachtregel.
if (-e ~/address/$name) then # Als bestand bestaat (-e),
cat ~/address/$name # Toon het bestand
else # Anders
echo ``no such file $name '' # Barf
endif # Einde if
echo ``---------------------'' # Kosmetische scheidingslijn tussen bestanden.
end # Bye-Bye
\end{verbatim}
\newpage
Laten we ons programma controlern door een \file{~/address} directory
aan te maken met daarin een aantal bestanden...
\begin{alltt}
\bash{mkdir ~/address}
\bash{cd ~/address}
\bash{echo hello1 zegt 123 \gt hello1 } # Maakt een bestand met de naam hello1.
\bash{echo hello2 zegt 456 \gt hello2 } # Maakt een bestand met de naam hello2.
\bash{echo hello3 zegt 789 \gt hello3 } # Maakt een bestand met de naam hello3.
\bash{phone.csh hello1 hello2 hello3}
hello1 zegt 123
------------------------
hello2 zegt 456
------------------------
hello3 zegt 789
------------------------
\end{alltt}
\subsection{Bash oplossing}
De \program{bash} oplossing in de oefening is niet zo vanzelfsprekend als
die onder \program{csh} vanwege de bizarre syntax van \program{bash}.
Probeer dit eens uit als je tijd hebt:
\begin{alltt}
#! /bin/bash # Vertel het systeem dat dit een bashscript is
if [ "\$#" -lt 1 ] ; then # Als aantal argumenten te weinig is
echo Usage: \$0 args # Geef het juiste gebruik
exit 1 # Exit met foutstatus
fi # "fi" sluit "if" statements
for i in \$@ ; do # Start een "do" loop op de \$@ array
if [ -f ~/address/\$i ];then # Als het bestand bestaat.
cat ~/address/\$i ; # druk het af,
else # Anders
echo There is no file \$i # Foutmelding als afwezig.
fi # Einde if
echo -------------- # Print een scheidingslijn tussen bestanden
done # "done" sluit de "do" loop
\end{alltt}
%$
\subsection{Oefeningen}
\begin{enumerate}
\item[\bf Oefening 1:] Maak de bovenstaande bashversie werkend.
\item[\bf Oefening 2:] Voeg een extra foutmeldingsregel toe voor als het bestand
niet bestaat.
\end{enumerate}
\newpage
\section{Algemene scripting}
In deze sectie zullen we verscheidene scriptprogramma's aanmaken met de
naam ``doubletake'' waarmee dubbele records in een lijst zullen worden
verwijderd. We zullen verscheidene nieuw
geleerde talen gebruiken om die taak te bewerkstelligen.
\subsection{\program{awk} versie}
Wijzig \file{doubletake} en plaats hierin de volgende regels:
\begin{alltt}
#! /bin/csh -f
# Dit script verwijdert dubbelen uit een gesorteerd bestand.
# Zie de \program{uniq} handleiding.
set file1 = \$1 # \$1 wordt de bestandsnaam.
cat $file1 | awk '\{ if ($0 != last ) \{print $0; \}; \}\{ last = $0; \}'
\end{alltt}
Het script slaat het eerste opdrachtargument op in \term{\$1'}.
Door \program{cat} toe te passen op het bestand, geeft het die uitvoer door
aan \program{awk}. Merk op dat de eerste regel
\term{\#! /bin/csh -f}
nodig is om het systeem te vertellen dat het inderdaad een \program{csh}
script is.
Het script dat \program{awk} gebruikt doet twee dingen.
Het controleert eerst om te zien of de variabele
\term{last} werd gezien, zo niet, druk het deze af. Het tweede wat het
doet is de huidige regel in \term{last} opslaan. Natuurlijk zou
je de gehele tweede regel op de opdrachtregel in kunnen typen, maar
daar is niet zoveel aan als je het 9 keer per dag moet doen..
\subsection{Perl Versie}
{\normalsize
\begin{alltt}
#! /usr/bin/perl -w
my \$line = ""; # declareer deze variabele.
foreach \$filename (@ARGV) # doorloop elke bestandsnaam in de ARGV lijst.
\{
open (SF, \$filename) or die "Can't open \$filename" ; # open het bestand en
geef een foutmelding als dit niet lukt.
while (<SF>) # Voor elke regel in het bestand
\{
chomp(\$_); # Verwijder aansluitende witruimte en/of linefeeds.
if ( \$line ne \$_ ) # ne = niet gelijk aan
\{
print "\$line \bs{n}"; # Ja, print me.
\}
\$line = \$_ ; # Reset \$line.
\}
\}
\end{alltt}
}
\newpage
\section{Script automatisering}
Shellscripts zijn geweldige hulpmiddelen wanneer je ze weet te gebruiken,
en kunnen heel wat tijd besparen. Als je applicaties of backups op even
uren of 's nachts moet draaien, wil je deze taak vast wel automatiseren
met behulp van \program{cron} tools, die we reeds in hoofdstuk
\ref{crontab} hebben doorgenomen.
De volgende oefening beschrijft hoe een scriptprogramma op te zetten om
het vuile werk voor je op te knappen wanneer je aan het golfen of slapen
bent.
\subsection{Scripts ordenen}
Voor je een taak aanlevert om te laten afhandelen door \program{cron},
moet je er zeker van zijn dat je de taak handmatig uit kunt voeren.
Dit bestaat uit het voorzien in de volledige paden naar de opdrachten
en het instellen van de juiste permissies. Laten we bijvoorbeeld eens
backups nemen. Je moet privileges hebben om als normale gebruiker
het tape device te beschrijven, tenzij je van plan bent je taken als
\root uit te voeren. Hier is een typisch script
\file{/home/guest/bin/backup.sh} dat een backup zou maken van je
homedirectory:
\begin{alltt}
#! /bin/bash
# Dit bestand heet backup.sh
/bin/mount /mnt/floppy # Mount de diskette.
cd /home/guest # Zorg dat je in /home bent.
/bin/tar cvfz /mnt/floppy/guest.tgz . # Maak een backup van je homedirectory.
/bin/umount /mnt/floppy # Unmount de diskette.
echo Voor elkaar baas!
\end{alltt}
In dit voorbeeld hebben we alle paden naar opdrachten opgegeven. Dit is
nodig aangezien je normale pad/shell in een script als deze niet wordt
aangeroepen. Dit script zal heel wat uitvoer genereren, wat later, wanneer
we ons crontab bestand aanmaken, naar een
logbestand of naar \file{/dev/null} moet worden doorgestuurd.
\newpage
\subsection{De scripts testen}
Je moet er zeker van zijn dat je de scripts handmatig kunt uitvoeren
voordat je ze met \program{cron} probeert aan te roepen. Op de meeste
Linux systemen is het niet toegestaan dat een gewone gebruiker
het \file{/mnt/floppy} device mount (beschrijft), tenzij je
\file{/etc/fstab} zodanig wijzigt dat toegang door gebruikers
is toegestaan:
\begin{alltt}
\bash{su -}
password: *******
\rootsh{vi /etc/fstab}
.......
/dev/fd0 /mnt/floppy msdos noauto,user 0 0
/dev/cdrom /mnt/cdrom iso9660 noauto,ro,user 0 0
:wq
\rootsh{exit}
\bash{./backup.sh}
\end{alltt}
\subsection{Oefeningen}
\begin{enumerate}
\item[\bf Ex 1:]
Zodra je er zeker van bent dat je script uit zichzelf zal draaien,
ben je zover het met \program{cron} in te stellen. Maak een crontab bestand
\file{/home/guest/crontab.backup} aan en lever het aan met
\bash{crontab crontab.backup}
zoals in sectie \ref{crontab}.
Vraag om hulp als je vastloopt.
\end{enumerate}
|