Shell scriptid - BASH shellis

Teejuht
1.
Mis on shelli scriptid?
2.
Minule tuttavad käsud ja nende võtmed
3.
Ülakomad - kolme sorti
4.
Muutujad
Käsurea argumendid
for tsükkel
5.
Aritmeetilised tehted ja input'i lugemine
6.
while konstruktsioon
7.
if ja test  tingimused
AND ja OR abil
8.
case konstruktsioon
9.
"kohmakas" käsurida
10.
Primary ja secondary prompt
11.
Praktlised scriptid ehk pikad käskude kombinatsioonid
otsi failid milles esineb string
failinimede väiketähestamine
ftp &
rekursiivne kataloogisisu
checksum - i leidmise script
 
 
Eeldused:

Siin lehel on eeldatud, et inimesed midagi juba oskavad: ehk on hea vaadata neid lehekülgi, kui on probleeme:

I/O ümbersuunamine: <, >, >>, <<, |, 1>/dev/null, >&1
loogilised tehted: AND, NOT, OR, XOR
protsessid: ps, PID, PPID, exit code

1. Mis on shell script?

Shell on see programm, mis käivitatakse peale süsteemi sisselogimist. Enamasti on selleks programm sh (bash), mis oskab reageerida  linuxi käskudele. Võimalik on shelliks panna ka teisi programme. Nt. telnet, pine, passwd.

Shelli scriptid on käivitamiseõigusega tekstifailid, kus on sees tuntud shelli käsud soovitud järjekorras. Näiteks selline:

dw.sh:

#!/bin/sh
echo "Tere kangelane klaviatuuri taga"
date
w

ning andes kasu dw.sh

kuvatakse ekraanile midagi sellist:

Tere kangelane klaviatuuri taga
Laupäev, 6. Juuni 1998 12:12:28 EET DST
imre
polla
maali
vladik

NB! et and failile käivitamisõigus tuleb anda korraldus

chmod u+rwx dw.sh

õigusi saab näha käsuga ls -l

See on primitivne näide aga ideed kannab.
NB! pange tähele, et shell script algab #! ja interpretaatori nimega /bin/sh.

Peab kohe ütlema, et shelli scriptide kirjutamiseks on vaja tunda linuxi käske ja nende võtmeid. Tihti on linuxi käsud juba iseenesest väga võimalusterohked ja õigetest võtmestest hulka kasu.

2. Minule tuttavad käsud ja nende võtmed
NB! kasude kohta saab linuxis abi kui man on installeeritud nii:

man kasunimi
man 5 kasunimi
(vahest on asja abileht jagatud sektsioonideks - 1, 2, 3 ....)

nt. man bash

Enamus siintoodutest tegelevad andmete stdout-i (standard output) saatmise ja soteerimisega. Esialgu on neid mõtet anda kasurealt ja vaadata mida nad teevad.

korraga võib käsureale anda rohkem kui ühe käsu kusjuures nad tuleb siis semikooloniga eraldada

who; date; uptime
 
Käsud ja võtmed
echo -n "Tere inimene" ei pane reavahetust jargi
echo -e "\n\tNagu C-s\n" tolgendab \-asju nagu C
nl < failinimi, voi nl failinimi nummerdab read
nl -n ln failinimi left justified (default)
nl -n rn failinimi rigth justified
nl -n rz failinimi right justified, leading zeros
cat -n failinimi nummerdab read
cat -A failinimi naitab koiki symbolied
cut -b 2-3 failinimi trykib iga rea 2 ja 3 -nda symboli
cut -f2 failinimi valjasta ainult teine tulp, eeldusel, et TAB on tulpade vahel
cut -d" " -f3 failinimi kuva 3. tulp ja eraldajaks on tühik
w -h  naita kasutajaid ilma p6iseta
sort filename voi sort -r failinimi sorteeri nagu votmetega antud
wc -l voi wc -w voi wc -c  kuvab vastavalt lines, word voi characters
du -S -a kuvab kataloogis olevate failide suurused
df disk free
free naitab malukasutust
top programm mis naitab palju - q lopetab
ps aux | grep imre mina näen nii enda protsesse
 

Toodutega saab päris palju ära teha, nt.

1. kuvame kataloogi sisu faili suuruste järjekorras

ls -la | sort +4n

2. ja failinimede tähestikulises järjekorras

ls -la | sort +8

3. kasutame samade tulemuste saamiseks käsku du:
(raalige välja mis nende vahel erinevat on!)

du -S -a | sort
du -S -a | sort -n
du -S -a | sort +0
du -S -a | sort +0n
du -S -a | sort +1
du -S -a | sort +1n

 

3. Ülakomad - kolme sorti - 'apostrophe', "apostrophe", `grave`

Ülekomade vahele satub tavalist kolme sorti asju:

a) tavalisi stringe (string - sõna või lihtsalt järgnevus sümboleid)

Minu nimi on Imre

b) käske

who

c) keskkonnamuutujaid

$HOSTNAME

Erinevad ülakomad interpreteerivad neid erinevalt - järgnevad selgitavad näited:
 

1.  'midagi vahel'  - märke nimetatakse inglise keeles apostrophe

echo 'who; date $HOSTNAME'

ei interpreteeri mitte midagi, lihtsalt kirjutab mis on nii nagu on.

2. "midagi vahel"  - double quote

echo "who; date $HOSTNAME"

ei interpreteeri käske vaid kirjutab sümbolid ekraanile; küll aga asendab keskkonnamutujad vastavate väärtustega

3. `midagi vahel`  - grave

echo `who; date`

interpreteerib kõiki käskudena kusjuures ei pane reavahetusi; et paneks tuleb näiteks nii teha

echo "`who; date`"

See pole veel kogu armastus aga suur kunst olla nende erinevate ülakomade sättimine.
 

4. Muutujate kasutaine shell scriptis.

 Scriptides saab kasutada ka muutujaid ning script saab oma tegevuse ajal küsida inputi ka mõnest failist või klaviatuurilt. No sciptis saab teha kõike mida käsurealt ja vist rohkemgi veel. Olgu üteldud, et mitmed nö. programmid pole tegelikult mitte binarid vaid hoopis shelli scripid. Nt. adduser uute kasutajate lisamiseks.
Shell scriptis saab kasutada konstruktsioone sarnaselt programmeerimiskeeltele: for, while, if, for ..
Ja muutujate nimed scriptides algavad nagu keskkonnamuutujate nimed dollariga, nt. $muutuja. Scriptides saab kasutada olemasolevaid keskkonnamuutujaid.

Järgnevad mõned näited.

1. käsurea kolm argumendid -script kirjutab millised olid  ja ütleb parameetrite arvu ning parameetrite väärtused:

#!/bin/sh
echo $3 $2 $1 $#

2. for tsükli näide, for tsükkel teeb iga nn. list toodud asjaga ühe ringi

#!/bin/sh
for i in mina sina tema meie teine nemad

do
echo $i
done

for tsükkel teeb oma ringi iga käesolevas kataloogis oleva objektiga

#!/bin/sh
for i in *

do
echo $i
done
 
5. Aritmeetilised tehted ja klaviatuurilt input. NB! expr juures on vaja tyhikuid panna!

#!/bin/sh
summa=0;
echo sisestage kaks arvu
echo -n "a: "
read a
echo -n "b: "
read b
echo a + b = `expr $a + $b`;
echo a - b = `expr $a - $b`;
echo a / b = `expr $a / $b`;
echo "korrutustehe tuleb kuidagi kavalalt teha";

teine võimalus aritmeetlilisteks teheteks:

#!/bin/sh
summa=0;
echo sisestage kaks arvu
echo -n "a: "
read a
echo -n "b: "
read b
echo "a +b = $(($a + $b))";
echo "a-b = $(($a - $b))";
echo "a / b = $(($a / $b))";
echo "a * b = $(($a * $b))";

ja kolmas

#!/bin/sh
summa=0;
echo sisestage kaks arvu
echo -n "a: "
read a
echo -n "b: "
read b
echo "a + b = $[$a + $b]";
echo "a - b = $[$a - $b]";
echo "a / b = $[$a / $b]";
echo "a * b = $[$a * $b]";

Ja jagamine töötab vaid täisarvude korral :(
 

6. While konstruktsioon - loeb kuni while taga olev lause lõpetab tegevuse edukalt (nii öelda exit code 0)

See script lihsalt kuvab stdin-i suunatud teksti, toodud kujul peab viimane rida lõppema reavahetusega.

kaivitada: scriptname < tekstifail

#!/bin/sh
while read reakene
do
echo $reakene
done

Ja see nummerdab lisaks read:

#!/bin/sh
nr=0;
while read reakene
do
nr=`expr $nr + 1`
echo $nr $reakene
done

See on lobus programm mis kasutab programmi `tr "vahemik" "vahemik"` mis muudab koik standart inputi tulevad vaiketahed suurteks

#!/bin/sh
while read rida
do
echo $rida | tr "a-z" "A-Z"
done

while nagu programmeerimises, NB! tyhikud ja nende puudumine on olulised, eriti expr real.

#!/bin/sh
mi=0;
while [ $mi -lt 6 ]
do
echo "mi: $mi";
mi=`expr $mi + 1`;
done

7. if - konstruktsioon ning test -imise tingimus (-gt, -lt, -e, -ne, -le,  -ge : greater than, less than, ..)
praktiliselt on ükskõik kas kasutada test $arv -gt 5 või [ $arv -gt 5 ]

#!/bin/sh
echo Sisestage arv:
read arv
if test $arv -gt 5; then
echo oli viiest suurem
else
echo oli väiksem võrdne viiega
fi
 
sama, aga ilma test võtmesõnata:

#!/bin/sh
echo Sisestage arv:
read arv
if [ $arv -gt 5 ]; then
echo oli viiest suurem
else
echo oli väiksem võrdne viiega
fi

aga nii ütelda "way too cool" võimalus kasutada tingimusi on järgmine: mängitakse loogiliste  AND  ja OR peale.

tingimus1 AND  tingimus2 mida kirjutatakse tingimus1 && tingimus2
tingimus1 OR  tingimus2 mida kirjutatakse tingimus1  || tingimus2

AND  tehe on  tõene vaid siis, kui mõlemad tigimused on tõesed.
AND on mittetõene kui kasvõi üks on mittetõene.
 

Vaatame kuidas arvuti tegeleb tehtega

kui (7 > 6 AND 6 < 7)
siis tee seda ja seda
vastasel juhul
toda ja toda

antud juhul tehakse seda ja seda.  7 on suurem 6 -st ja 6 väiksem 7 st.
tingimust loeb arvuti vasakult paremale:
Antud juhul veenub masin, et 7 on suurem 6 -st ja asub kontrollima kuidas on lugu 6 < 7 tõesusega. Veenududes et mõlemad on tõesed tuleb arvuti järeldusele, et AND on tõene. Ja nii viiakse täide seda ja seda.
Praktiliselt on oluline see, et kui vasakul pool olevad käsud lahenevad nö. positiivselt, siis täidetakse ka paremal olevad käsud.

Vaatame aga teist olukorda:

kui (7 > 8 AND 6 < 7)
siis tee seda ja seda
vastasel juhul
toda ja toda

antud juhul viiakse täide korraldused toda ja toda kuvõrd AND tehe annab ebatõese tulemuse. Ning vaatleme kuidas arvuti sellisele järeldusele tuleb ning AND tehet teeb.
Esmalt teeb ta kontrolli kas 7 > 8, leiab et see on väär. Kuivõrd AND tõesuseks on vajalik mõlema tingimuse (vasakul ja paremal AND-st) tõesus siis arvuti ei hakkagi teist poolt ( 6 < 7) kontrollima sest AND on nagunii mittetõene.
Praktiliselt on oluline see, et kui vasakul pool olevad käsud lahenevad nö. negatiivselt, siis paremat poolt ei asuta täitma.

konkreetne naide:

Teeme ls -i kataloogis kus on failid mina sina tema meie teie nemad
mina
sina
tema
meie
teie
nemad

ning nüüd:  ls | grep tema

tema

aga nüüd: ls | grep tema 1>/dev/null && echo "olemas"

olemas

Selgitus: et vasakul pool && marke oleva konstruktsiooni viimane käsk andis positiivse tulemuse (st. leiti mida otsiti), siis on vasaku poole väärtus mingis mõttes tõene.  Kuigi meid praktiliselt ei huvita loogilise tehte (tingimus1 && tingimus2) väärtus arvuti siiski leiab selle. Sellel AND tehte tegemise teel ta praktiliselt täidab paremal pool olevad käsud.

Sarnaselt OR tehe

ls | grep tema 1>/dev/null || echo "ei ole sellist"

mitte midagi

ls | grep tartu 1>/dev/null || echo "ei ole sellist"

ei ole sellist

Selgitus on sarnane eeltoodule. OR tehe on tõene kui

1. tingimus1 on tõene ja tingimus2 väär
2. tingimus1 on väär ja tingimus2 tõene
3. tingimus1 on tõene ja tingimus2 tõene

Niisiis, kui vasakul poole oleva tehete väärtus on tõene, siis on OR tehte tulemus niikuinii tõene ja paremal pool olevat asja ei kontrollita (käske ei täidata).
Praktiliselt on oluline see, et kui vasakul pool olevad käsud lahenevad negatiivselt ainult siis täidetakse parema poole käske.

Seda kas mingi käsk lahenes nii öelda positiivselt või negatiivselt saab lihtsasti teada. Arvuti salvastad viimase tulemuse keskkonnamuutujasse  $?.
Proovige nt.
ls
echo $?
0

date
echo $?
0

ls leninvladimir $?
echo $?
1
(eelsusel, et sellist faili ei ole)

Siin ongi nüüd see jama, et õnnestumisel omandab $? väärtuse 0 ja muidu mingi teise arvu (nö. exit code). Tavaliselt on C -s õnnestumise väärtus 1.  No traditsioonid, traditsioonid ...

Esitame veel ühe programmi paraleelselt kahel moel realiseerituna:
Programm küsib arvu ja ütleb on see viiest suurem või mitte
 

#!/bin/sh
echo -n "Sisestage arv: "
read arv
if test $arv -gt 5; then
echo "oli viiest suurem"
fi

#!/bin/sh
echo "Sisestage arv: "
read arv
test $arv -gt 5 && echo "oli viiest suurem"
 
8. case konstruktsioon

üks praktiline näide case'st:

#!/bin/sh
echo "Programm töötab lõpmatus tsüklis, väljumiseks C-c :)"
while :
do
echo -n "sisestage a: "
read a;
case $a in
[a-z]*) echo "väikese tähega algav";;
[A-Z]*) echo "suure tähtega algav";;
*) echo "midagi muud kui tähtega algav";;
esac
done
 

näide kuidas teha valikuvõimalusi koos vaikimisivalituga:

suvi:~/ooo# k1.sh
Kuhu sõita soovite:
        Tartu
        Tallinn
        Pärnu
Sisestage palun valik [Tartu]:
 

#!/bin/sh
vt=0;
echo "Kuhu sõita soovite:"
echo -e "\tTartu\n\tTallinn\n\tPärnu"
while [ $vt -ne 1 ]
do
vt=1;
echo -n "Sisestage palun valik [Tartu]: ";
read valik
case $valik in
"") valik="Tartu"
;;
"Tartu") valik="Tartu"
;;
"Tallinn") valik="Tallinn"
;;
"Pärnu") valik="Pärnu"
;;
*) vt=0
;;
esac

if [ $vt -eq 1 ]; then
        echo "Valsite $valik, tore";
else
        echo "paha lugu, valige võimaluste seast ...";
fi

done

Muuseas, kui teha programm mis töötab võtmetega siis nende võtmete kontrolli olla case-ga hea teha.

9. Käsurealt "kohmakate" käskude andmine:

1. timer, nali - legaalne on anda korraldus:

bash# i=0; while [ $i -le  5 ]; do echo -n "$i"; date; sleep 1; i=`expr $i + 1`; done
 
See on nagu script ise.

2. Script nummerdab kataloogis olevate failide nimed ja kuvab need tulbas

bash# a=0; for i in *; do a=`expr $a + 1`; echo $a $i ; done;

3. Script teeb sama mis eelminegi ainult failid esitatakse suuruse järjekorras

bash# a=0; for i in ` ls -l | sort +4n | awk '{print $9}'`; do a=`expr $a + 1`; echo $a $i ; done;

4. Mul oli selline probleem, et kasutati uut CD-Recorderit ja ma polnud kindel ka minu kataloogitäis kraami ikka sai veatult CD pinna peale. Ja halb oli see, te failinimede tähesuurust oli muudetud (st. reastus mida ls näitab oli muutunud kui võrrelda originaalkataloogi jaseda mis CD peal).
Tavaliselt kasutatakse data võrdlemiseks programmi diff -r kataloog1 kataloog2 aga mina seda teha ei saanud sest failide nimed ja järjekord polnud ühesugune. Niisiis,

1. kasutasin alltoodud scripti ja nimetasin failide nimed ümber (tehes muidugi enne koopia :) lihtsalt 1, 2, 3 suuruse järjekorras (see eeldab, et kahte ühesuurust faili ei ole)

bash# a=0; for i in ` ls -l | sort +4n | awk '{print $9}'`; do a=`expr $a + 1`; mv $a $i ; done;

2. kasutasin diff-i vastavatele kataloogidele

10. Primary ja secondary prompt (tahistame PS1$ ja PS2>)

Minul isiklikult puudub selle võimaluse  järgi karjuv vajadus aga huvitav siiski:
kui anda selline korraldus prompti taha:

PS1$ while [ 1 ]; do date; sleep 1; clear; done

siis ilmub terminalilt yles aarde date mida iga sekund refresh'itakse

aga andes korralduse ridahaaval saab ka:

PS1$ while [ 1]
PS2> do date
PS2>sleep 1
PS2>clear
PS2>done

miks ta nii kaitub on ehk selle parast, et while [ 1 ] lõhnab shelli jaoks väga lõpetamata lause järele ja ta on valmis seda lõppu vastu võtma.
C - c lõpetab (Ctrl -c)

11. raktilised scriptid ehk pikad käskude kombinatsioonid

Siin on esitatud mõned (kõik kolm :) minu meelest mõnusat scripti mis on abiks olnud:

1. Kuvab tulpa kataloogis olevate nende failide nimed milles string esineb:
fstr.sh:

#!/bin/sh
for a in `ls $1 | awk '{print $9}'`; do cat $a 2>/dev/null | grep $2 >/dev/null && echo "leidis: $a";  done

kaivitada:
fstr.sh kustkataloogistotsida midaotsida

2.  Probleem: kataloogis on  ainult failid ja nende nimedes on igal vähemalt üks suur täht.  Script
asendab kataloogis olevate faililide nimed samade nimedega, ainult kõik tähed on väikesed.

captolo.sh

#!/bin/sh
kataloog=$PWD;
cd $1;
for i in `ls`
do
echo $i;
mv $i `echo $i | tr "[A-Z]" "[a-z]";`
done;
cd $kataloog;
 
Kasutus:

captolo.sh katalooginimimillesonneedfailid

3. ftp - tegeija script. Kasulik kui on modemi ühendus ja on soov mingist kolmandast masinast faile kopeerida teise masinasse.
Kõigepealt logite neisse mõlemasse sisse, vatate kus kataloogides midagi on, kas õigused on korras ja kas mahuvad ära.
Seejarel lahete teise masinasse mis tõmbama hakkab ja redigeerite vastavalt scripti.
Ning siis jätate scripti tööle

ptf.sh:

#!/bin/sh
ftp -n -i ftp.funet.fi <<LOPP
quote user ftp
quote pass imre@
cd pub/Linux/mirrors/slackware/slakware/d1
lcd /home/imre/slackware/d1
bin
mget *
LOPP

NB! peale LOPP -u lõpus tuleb reavahetus panna. Siin on muidugi see puudus, et ta ei konda kataloogistruktuure mistahes sügavusele alla välja :)
 
Kasutus:

ptf.sh &

Ja veel üks ftp -tegeija - natuke instelligentsem. Failis kataloogid on kirjas sama taseme kataloogid
(nt. slackware distribution):

kataloogid:
a1
a2
a3 ...

ja script on ise selline, f.sh:

#!/bin/sh

for i in `cat kataloogid`
do
ls
cd /home/imre/data
mkdir $i
cd $i
`ftp -n -i ftp.funet.fi <<LOPP
quote user ftp
quote pass imre@
cd "/pub/Linux/mirrors/slackware/slakware/$i"
bin
mget *
LOPP`
done
 

4. Script mis peaks kirjutama ekraanile kataloogis sisalduvate asjade suurused ja pikad nimed ilusti tulbas. Ta alati ei tööta õieti, millegipärast. Eks ise kontrollige. Ta on rekursiivne, st. ise ennast vajadusel uuesti käivitav.

liz.sh

#!/bin/sh
current="$PWD";
cd $1;
ls -Fdl $PWD/* 2>/dev/null;
for i in `ls -d $PWD/* 2>/dev/null`
do
if (! test -L "/$i") && (test -d "/$i"); then
liz $i;
fi
done;
cd $current;

Kasutus:

liz.sh katalooginimi

5. cksum -programmi kasutamine. See on küll praegu ühe erijuhu jaoks - kui kataloogison vaid failid ja mitte alamkatalooge aga ikkagi.

c1.sh:

#!/bin/sh
cd $1
for item in *
do
echo `cksum $item`
done

Kasutus:

c1.sh kataloog.kus.asuvad.failid

Sama asi ainult juhuks kui on Kataloog miles on vaid alamkataloogid ja neis omakorda vaid failid (nt. slackware distribution :)

c2.sh:

#!/bin/sh
cd $1
for i in *

do

cd $i
for k in *

do
echo `cksum $k` $i |  awk '{print $1" "$2" ./"$4"/"$3}'
done

cd ..

done
 

Kasutus:

c2.sh kataloog.milles.on.alamkataloogis.ja.neis.failid

Ja nende scriptide output on mõistlik suunata faili ning siis pärast originaali ja enda oma võrrelda nt. diff, cmp, wc abil. Mitte käsitsi!