Skall

Bourneshell-familien
sh
Bourne shell
ksh
Korn Shell
bash
Borne again shell (GNU)
POSIX sh
ligner mest på ksh
Andre

hello world

echo Hello world
printf 'Hello world!\n'
Bruk printf hvis du vil utelate linjeskift til slutt, eller noe mer avansert. (Unngå varianter med echo -n eller \c. Disse er ikke portable.)
#! /bin/sh

printf "hva heter du? "
read navn
echo "hei $navn"

redirigering

Når du starter et program vil det få input og output.

Kjører du programmet interaktivt vil input og output gå til «terminalen» du starter programmet fra. Ved å bruke redirigreingsoperatorene kan vi styre input og output til andre steder:

> fil
Styrer output til fil (overskriver)
>> fil
Styrer output til fil, append (legger til på slutten)
< fil
Styrer input fra fil
tr -d '\r' <dosfil >unixfil
ls >> filer
prog1 | prog2
«Pipe» Styrer output fra prog1 til input på prog2

Analogien er at vi hekter et «rør» fra ut-hullet på det ene programmet til inn-hullet på det andre programmet. På denne måten kan vi lage lange «pipelines» med programmer.

filtre

Et filter er et program som er beregnet til å stå i en pipe. Det får input, gjør noe med denne og sender resultatet ut. Du kan dermed se på programene bortover pipen som statements som gjør noe med dataene som strømmer igjennom. Filtre er ofte (men ikke alltid) linjebaserte.

Under følger en del programmer som er nyttige som filtre, og beskrivelse av hva du kan gjøre med dem. For å få full oversikt, se mansiden til hvert enkelt program.

man grep

grep

Plukker ut linjer som matcher et mønster. Kan brukes alene
grep geitemat Mailbox mail/*
... eller i en pipeline
ypcat passwd | grep Lise

wc

wc (word count) teller ord, og linjer og tegn.
wc exphil/modul2.txt
ypcat passwd | grep ':/bin/bash$' | wc -l

cut

ypcat passwd | cut -f7 -d: | grep bash | wc -l

sort

Hva blir disken brukt til?
du -S | sort -nr | less

uniq

Hvilke skall er i bruk?
ypcat passwd | cut -f7 -d: | sort | uniq
Og hvor mange av hvert?
ypcat passwd | cut -f7 -d: | sort | uniq -c | sort -nr

tr

ypcat passwd | cut -f5 -d: | tr '{|}[\\]' 'æøåÆØÅ' | sort
ypcat passwd | cut -f5 -d: | cut -f1 -d, | tr '{|}[\\]' 'æøåÆØÅ' | 
   awk '{print $NF,$0}' | sort | awk '{print $2,$3,$4,$5,$6,$7}' > navneliste

awk

pattern { action }
iostat 1 500 |
awk 'NR>2 {total++; if ($4 > 0) buzy++; } END {print buzy/sum }'
awk 'BEGIN {produkt=1} {produkt*=$1} END {print produkt}'

sed

sed 's/NTH/NTNU/g'

Liksom-grep:

sed -n '/regexp/p'
er det samme som:
grep regexp

(grep har navnet sitt fordi du i ed bruker g/regexp/p for å skrive alle linjer som matcher regexp.)

cat

Cat brukes til å conCATenere filer.
cat kapittel*.xml > bok.xml
Hvis du ønsker å spare på CPU-syklene: Ikke misbruk cat til å starte en pipe, der du kunne brukt < istedet.

tac [*]

Reverserer linjerekkefølgen fra input til output
tail logfil | tac | less

tee

Tee tar en kopi av input til en fil i tillegg til sende den videre til output.
make | tee makelog

find

find /home/prosjekt -mtime -1 -print | wc

xargs

find og xargs brukes mye sammen.
find . -name core -print | xargs rm
Dersom du bruker find og xargs kan det være lurt å bruke \0 til å skille filnavnene istedet for linjeskift. [*]
find . -name core -print0 | xargs -0 rm

seq [*]

seq teller opp en sekvens:
$ seq 4
1
2
3
4
Seq er spesielt nyttig sammen med for-løkker.
for i in $(seq 99 0); do
  echo "$i kagger med øl på en hylle..."
done

Eksempel på oppbygging av pipeline

Programmet rwho gir en output av linjer på dette formatet:
steinarh lizard:ttyp6     Nov  8 11:22 :05
Hva kan vi gjøre:
rwho | wc
rwho -a | wc
rwho -a | cut -f1 -d\  | sort | uniq | wc
rwho -a | cut -f1 -d\  | sort | uniq -c | sort -nr | less
rwho -a | awk '{print $2}' | cut -f1 -d: | sort | uniq -c | sort -nr

kommandolister

Du kan bygge lister av kommandoer med operatorene: ; & || &&
;
Funker på samme måte som ny linje. Dersom du slår sammen to linjer til en skal du nesten alltid bruke ; (unntaket er etter do, then, else eller { )
&
Skallet begynner på neste statement mens forrige kjører i bakgrunnen.
||
Logisk eller. Vi gjør det som kommer etter || bare dersom det før feilet.
&&
Logisk og. Vi gjør det som kommer etter && bare dersom det før gikk bra.
ls ; date
er det samme som
ls
date

Returverdi

Alle programmer har en returverdi. Dersom programmet mener det fikk til det det skulle, returnerer det 0, hvis ikke returnerer det et tall > 0.
$ true
$ echo $?
0
$ false
$ echo $?
1

true og false er to småprogrammer hvis eneste oppgave er å hhv lykkes og feile. $? er en magisk variabel med returverdien til der forrige programmet som ble kjørt (startet).

Det er vanlig å ha : som et internt alias for true.

test

Test er et program som tester en ting og returnerer true eller false utifra om det stemmer eller ikke.

Noen få av tingene test kan teste:
-e fil
Sann hvis fil finnes.
-d fil
Sann hvis fil finnes og er en katalog.
-f fil
Sann hvis fil finnes og er en normal fil.
-r fil
-w fil
-x fil
Sann hvis fil finnes og er lesbar/skrivbar/kjørbar.
-s fil
Sann hvis fil finnes og har en størrelse >0.
fil1 -nt fil2
fil1 -ot fil2
Sann hvis fil1 er nyere/eldre enn fil2.
fil1 -ef fil2
Sann hvis fil1 og fil2 er samme fil (samme inode og device).
-z streng
Sann hvis streng er tom.
-n streng
streng
Sann hvis streng ikke er tom.
! expr
Sann hvis expr er usant
expr1 -o expr2
Sann hvis en av expr1 og expr2 er sann.<
expr1 -a expr2
Sann hvis både expr1 og expr2 er sann.<
num1 -eq num2
num1 -ne num2
num1 -lt num2
num1 -le num2
num1 -gt num2
num1 -ge num2
Sann hvis num1 er lik / ulik / mindre enn / mindre eller lik / større enn / større eller lik num2
test -f logfil && cp logfil logfil.bak 

Test kan også startes som [, og forventer da å finne en ] å slutten av argumentlista si.

[ -n "$foo" ] && bar="$foo"

struktur

for

for variabel in ord ...
do
  ...
done
For setter variabel til neste ord i lista for hvert gjennomløp.
cd /var/log
for file in syslog printerlog maillog; do
  [ -f $file.2 ] && mv $file.2 $file.3
  [ -f $file.1 ] && mv $file.1 $file.2
  [ -f $file   ] && mv $file $file.1
done

if

if betingelse
then
  ...
fi
Du kan også bruke else og elif sammen med if.
if [ -z "$foo" ]; then
  if [ -n "$bar" ]; then
    foo="$bar"
  elsif [ -n "$baz" ]; then
    foo="$baz"
  else
    foo=default
  fi
fi  

while

while betingelse
do
  ...
done
while true; do
  cat vekkeklokke.au > /dev/audio
  sleep 10
done

case

case ord in
uttrykk) ... ;;
esac
case $filnavn in
*.gif|*.jpeg|*.png) xv $filnavn ;;
*.mp3) mpg123 $filnavn ;;
*.avi) mplayer $filnavn ;;
*) echo "Unknown filetype" 1>&2 ;;
esac

funksjoner

blopp()
{
  echo "her er jeg"
}

program- og funksjonsparametre

$0
Inneholder navnet på programmet
$1 $2...
Argumenter til programmet
shift
shifter alle argumenter, slik at $1=$2 $2=$3 ...
$*
Inneholder alle argumenter i en streng
"$@"
Inneholder alle argumenter bevart i en liste (tilsvarer "$1" "$2" ...)
ls() {
  local file
  for file in *; do
    echo $file
  done
}
cat() {
  local file line
  for file in "$@"; do
    while read line; do
      echo $line
    done < $file
  done
}

Gruppering

Du kan gruppere kommandoer med () eller {}
( sleep 1; rm /notexists ; true ) >/dev/null 2>&1 || echo jada
{ sleep 1; rm /notexists ; true; } >/dev/null 2>&1 || echo jada

substitusjon og variable

$( ) ` `
kommandosubstitusjon
$(( ))
aritmetisk substitusjon
${ }
variabelsubstitusjon

kommandosubstitusjon

echo Du har følgende filer i denne katalogen: $(ls)

aritmetisk substitusjon

c=$(( $a + $b * 3 ))
logrotate:
#! /bin/sh

condmv() {
  [ -f "$1" ] && mv "$1" "$2"
}

cd /var/log
for file in "$@"; do
  for e in $(seq 5 1); do
    condmv "$file.$e" "$file.$(($e + 1))"
  done
  condmv "$file" "$file.1"
done
kill -HUP "$(cat /var/run/syslog.pid)"

variabelsubstitusjon

Brukes til å skille variabelnavn fra omkringliggende tekst:
echo super${var}plosjon
Innebygd tekstsubstitusjon:
${#parameter}
Antall tegn i variabelinnhold
${parameter%word}
${parameter%%word}
Ta bort tegn fra enden av variabelinnhold
${parameter#word}
${parameter##word}
Ta bort tegn fra starten av variabelinnhold
for i in *.pnm; do
  convert $i ${i%%.pnm}.jpeg
done
Default values:
${parameter:-word}
Bruk default verdi
${parameter:=word}
Bruk og sett default verdi
${parameter:?feilmelding}
Feilmelding hvis variabel ikke er satt.
${parameter:+word}
Bruk alternativ verdi
navn=${navn:-"$USER"}

Globbing

Globbing er når vi ekspanderer er shelluttrykk til filnavn.
?
Ekspanderer til ett vilkårlig tegn
*
Ekspanderer til et vilkårlig antall vilkårlige tegn
[abc]
Ekspanderer til et tegn fra et sett med tegn
$ echo k[uy]rs-*-del?.ps
kurs-globb-del1.ps
kurs-globb-del2.ps
kyrs--del2.ps

Quoting

To typer quoting:
' '
Hard quoting. Ingenting innenfor blir ekspandert.
" "
Mjuk quoting. Variabel-, aritmetisk- og kommando-ekspansjon blir utført. Ingen globbing eller splitting av utrykket.
\
Quoter neste tegn.
$ fisk=sei
$ touch seipostei
$ echo ${fisk}*
seipostei
$ echo "${fisk}*"
sei*
$ echo '${fisk}*'
${fisk}*

Lister

$ spam=(foo bar baz)        [*]
$ echo $spam
foo
$ echo ${spam[0]}
foo
$ echo ${spam[2]}
baz
$ echo ${spam[*]}
foo bar baz
$ spam[1]=gris; spam[3]=rotte
$ echo ${spam[*]}
foo gris baz rotte
Slett ikke alle borneshell har lister. Featuren kommer fra Kornshell, finnes dermed i "POSIX sh". Syntaksen spam=(foo bar baz) finnes i tillegg bare i bash2. Selv om ditt bourneshell ikke har navngitte lister, så har det en liste, nemlig argumentlisten. (Den som normalt inneholder argumentene til programmet eller funksjonen.)
$ set -- foo bar baz
$ echo $1
foo
$ echo $3
baz
$ echo $*
foo bar baz

Prosesskontroll

Fordi vi omtrent bare bruker eksterne programmer i shellscript, er prosesskontroll viktig.

spesialvariable

$$
pid til selve skallet/scriptet
$!
pid til forrige prosess som ble startet i bakgrunnen
$?
returverdi fra forrige program som ble kjørt i forgrunnen
program &
programpid=$!
sleep 60
kill $programpid
( sleep 100 ; kill $$) &

Kill

Kill sender et signal til en prosess. Dersom du ikke oppgir signal vil den sende TERM, som er en beskjed til prosessen om å avslutte (terminere).
kill [ -signal ] pid
kill -l gir en liste over tilgjengelige signaler.

Trap

Hva skjer når du sender et signal til en prosess?

Alle signaler har en default action, som for de fleste signaler går ut på å avslutte (med eller uten coredump), noen ignoreres som default, og noen håndteres av operativsystemet.

Dersom prosessen selv håndterer signalet, kan vi bruke trap for å endre oppførselen når vi mottar dette signalet.

trap action sig ...
Vanlig å bruke for opprensking dersom et program blir drept:
tempfil=/tmp/foo.$$
trap "rm -rf $tempfil; exit 1" HUP
# gjør noe med $tempfil
Hva skjer?
trap "echo foo" TERM
(sleep 4; kill $$) &
echo bar
sleep 10
echo baz

Wait

Wait gir muligheten til å vente på en barneprosess.
wait pid
Hva skjer?
sleep 10 &
p=$!
(sleep 4; kill $p) &
echo bar
wait $p
echo baz

redirigreringsmagi

Vi har mer enn output og input!

Når vi starter et program arver programmet fildeskriptorer fra mor-prosessen. Vanligvis vil vi da få minst stdout (fildeskriptor nr 0), stdin (1) og stderr (2). (Disse tre blir satt opp på en terminal, og i de fleste andre kontekster et program kjøres fra.)

Stderr unnslipper pipelinen slik at feilmeldingen kan gå direkte til brukeren i stedet for å forsvinne et sted i røret.

Mer redirigering

Ved å bruke nummeret på en fildeskriptor kan du manipulere de slik du ønsker
fd> fil
Overskriver fil fra fd
fd>> fil
Appender fd til fil
fd< fil
Henter input fil til fd
fd1>&fd2
Gjør fd1 til en kopi av fd2

Pass på når du bruker >&

program >/dev/null 2>&1
men
program 2>&1 | program2
Hvorfor? Vi må se på det som tilordning.
echo foo 3>&1 >/dev/null 1>&3

Noen spesielle skall (f.eks es) bruker = istedet for >&, da er det litt enklere å skjønne. Se på både > og >& som en tilordning til fildeskriptoren til venstre.

Vi kan lure en redirigering til å komme fra en fil:
diff -u <(sort /etc/passwd) <(ypcat passwd | sort)
Dette er ofte implementert via noen spesielle filer i /dev:
/dev/fd/0 /dev/fd/1 /dev/fd/2 ...

environment

export TMPDIR
Merker variabelen TMPDIR som eksportert. Alle programmer som startes fra nå vil få TMPDIR som environmentvariabel.
FOO=bar prog
Vi gir akkurat dette programmet en environmentvaiabel FOO (satt til bar).

$PATH

Dette er stien som skallet leter etter programmer i.

$IFS

Internal Field Seperator. Dette er en magisk variabel. Første tegn i denne blir brukt som skilletegn når du ekspanderer lister (med $* eller ${foo[*]}. Alle tegn i denne blir brukt når du setter lister.
OLDIFS="$IFS"
IFS="a b"
set -- hahahah jeg ler jjjjbhhh
echo $*
echo "$*"
IFS="$OLDIFS"
Du trenger ikke vite noe om $IFS, bortsett fra at du kan få trøbbel hvis du roter med den ;-)

Metaprogramering

for i in .qmail* ; do
  printf '0a\n|spamfilter\n.\nw\n' | ed $i
done
for i in *.pnm; do
  convert $i ${i%%.pnm}.jpeg
done
find -name \*.pnm | sed 's/\(.*\).pnm/convert & \1.jpeg/' | sh -x
for i in $(grep -l '^Signature .*steinarh@fm.unit.no' */registration) ; do
  printf '/^Signature/s/steinarh@fm.unit.no/steinarh@math.ntnu.no/\nw\n' |
    ed $i
done
sed -n 's/^Signature \. \.\.\. \.\.\. : //p' */registration \
     | sort | uniq -c | sort -nr > ~/top_ten.txt

source

Operatoren . brukes til å source filer, dvs inkludere fila i shellscriptet som utført. Dette brukes bl.a. til oppstartsfiler, men vi kan bruke det til hva som helst, også å source en fil som nettopp er blitt laget.
input=/tmp/sourcetest.$$
trap "rm -f $input; exit 1" HUP TERM
cat > $input
. $input
rm -f $input

eval

Eval vil utføre koden i argumentene du gir den.
$ grukk=foo
$ gaff=bar
$ eval "$grukk=$gaff"
$ echo $foo
bar
while [ $# -gt 0 ]; do
  case $1 in
  -i) eval "$2"; shift ;;
  *) echo "Illegal option \"$1\"" 1>&2 ;;
  esac
  shift
done

Vanlige feil

filnavn="/home/lise/mine dokumenter/handleliste.txt"
rm $filnavn
Bruk rm "$filnavn". Bruk alltid "$@" isteden for $*.

Hvis /var/log/old ikke finnes...

cd /var/log/old
rm *
Sjekk etter feil på kritiske operasjoner:
die() { echo "$@" 1>&2 ; exit 1 }
cd /var/log/old || die Can't change directory to /var/log/old
rm *

Sikkerhet

Dette programmet har en race-condition
...
[ -e /tmp/mail_signature ] && { echo "File exists!" 1>&2 ; exit 1 }
sed '/^ --/,$p'  /tmp/mail_signature
...
Som kan utnyttes av dette...
while true; do
   ln -s /etc/passwd /tmp/signature
   rm /tmp/signature
done

SLUTT!

http://www.pvv.ntnu.no/~steinarh/shkurs/kurs.html