File: scripting.tex

package info (click to toggle)
doc-linux-nl 20051127-2
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 16,408 kB
  • ctags: 94
  • sloc: xml: 47,403; makefile: 312; perl: 193; sh: 116; ansic: 12; csh: 9
file content (397 lines) | stat: -rw-r--r-- 14,266 bytes parent folder | download | duplicates (2)
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}