Unix Shell-Programmierung



EDV-Zentrum der Universität für Bodenkultur

Bourne-Shell

cron

sed

awk

Kursunterlage

H. Partl

Juli 1995



U n i x

Shell-Programmierung

Bourne-Shell

Shell-Scripts

vi filename     erstellen

chmod 700 filename

sh -vx filename parameter 2>&1 | more (sh)
sh -vx filename parameter |& more (csh)
      testen

filename parameter   aufrufen

#!/bin/sh     magic number (1. Zeile)

#       Kommentar
  zumindest:   Name
      Zweck
      Autor
      Datum (Version)

if [ $# -lt 2 -o $# -gt 3 ]
then   echo "Usage: filename par1 par2 [par3]"
  exit 1
fi

Parameter

$1     1. Parameter

$9     9. Parameter

    oder Leerstring

"$1"     1. Parameter mit ""

$*     alle Parameter

"$@"     alle Parameter, jeder mit ""

$#     Anzahl der Parameter

shift

name=${1-defaultwert}

Variable

name=wert

$name

${name}

${name-defaultwert}

export name

if

if   command
then   commands
fi

if   command
then   commands
else   commands
fi

if   command
then   commands
elif   command
then   commands
else commands
fi

test

if   test expression

if   [ expression ]

-d file       Directory

-f file       File

-s file       nicht leeres File (size)

-r file       Read-Permission

-w file       Write-Permission

-x file       Execute-Permission

-z "string"     Leerstring (zero)

=       Strings gleich

!=       Strings ungleich

-eq       Zahlen gleich (equal)

-ne       Zahlen ungleich

-gt       größer (greater than)

-ge       größer gleich

-lt       kleiner (less than)

-le       kleiner gleich

!       nicht

-a       und (and)

-o       oder (or)

\( \)       Teil-Expressions

while

while   command
do
  commands
done

read

while read feld1 feld2 rest
do
  commands mit $feld1 $feld2
done < filename

expr

i=1
n=10
while [ $i -le $n ]
do
  commands mit $i
  i='expr $i + 1'
done

for

for name in wert1 wert2 wert3
do
  commands mit $name
done

case

case wert in
muster1)   commands ;;
muster2)   commands ;;
*)     commands ;;
esac

exit

exit 0     erfolgreich beenden

exit 1     mit Fehler beenden

exit     Exit-Status des letzten Befehls

I-O-Redirection und Pipes

command <infile

command <<eofstring
... Eingabe-Zeilen ...
eofstring

command >outfile

command >>outfile

command >outfile 2>errfile

command >outfile 2>&1

echo "Fehlermeldung" 1>&2

command1 | command2 | command3

command1 | tee outfile | command2

File-Globbing

*     0 bis n beliebige Zeichen

?     1 beliebiges Zeichen

[abc]     1 Zeichen aus Menge

[a-z]     1 Zeichen aus Menge (Bereich)

![abc]     1 Zeichen nicht aus Menge

Maskierung (Quoting):

\x     1 Zeichen

'text'     ganzer Text

"text"     Text bis auf $ und '

'command'   Ergebnis des Befehls

Sub-Shells

(date; who) >file

(cd sub; ls -l | more)

(cd old; tar -cf - .) | (cd new; tar -xvf - .)

crontab

crontab -l

crontab -l > file
crontab < file

Minute Stunde Tag Monat Wochentag Befehl

1   0   1   *   *   command

jeden Monatsersten um 0:01 Uhr

30   19   8   3   *   command

am 8. März um 19:30 Uhr

1   0   1 1,4,7,10   *   command

an jedem Quartalsbeginn

0   5   *   *   1   command

jeden Montag um 5 Uhr

0   5   *   *   0,6   command

jeden Sonntag und Samstag

0   5   *   *   1-5   command

jeden Montag bis Freitag

sed

Stream Editor

sed -e 'command' textfile

sed -f commandfile textfile

sed -e 'command' inputfile >outputfile

Editor-Befehle wie bei ed und ex

Regular expressions

^     Zeilenanfang

$     Zeilenende

.     1 beliebiges Zeichen

[abc]     1 Zeichen aus Menge

[a-z]     1 Zeichen aus Menge (Bereich)

[^abc]     1 Zeichen nicht aus Menge

*     0 bis n solche Zeichen

.*     0 bis n beliebige Zeichen

awk

Alfred   V.   Aho
Peter   J.   Weinberger
Brian   W.   Kernighan

awk 'command' textfile

awk -f commandfile textfile

awk 'command' inputfile >outputfile

bedingung { aktion }

$1     1. Feld der Zeile

$99     99. Feld der Zeile

$0     Zeile

"wert"     Text-String

wert     Zahl

NF     Feld-Nummer

NR     Zeilen-Nummer (Record)

FS     Feld-Separator

==     gleich

<     kleiner

<=     kleiner gleich

>     größer

>=     größer gleich

~     enthält Teilstring

!     nicht

&&     und

||     oder

BEGIN     vor der ersten Zeile

END     nach der letzten Zeile

print     Ausgabe

Online-Manuals

man -k keyword

man commandname

man chapter commandname

man ... | col -bx | lp -ddruckername

1     Befehle für Benutzer

1M     Befehle für Systemadmin.

2     C-System-Calls

3     C-Library-Funktionen

3F     Fortran-Library-Funktionen

4     File-Formate

5     verschiedenes

7     Special-Files (Devices)

8     Befehle für Systemadmin.

Beispiele für Shell-Scripts

ll

#! /bin/sh

#

# ll = ls -l (like under HP-UX)

#

# by Hubert Partl (EDV-Zentrum Boku Wien)

# last change: 1993-01-07

ls -l $*

exit

tel

#!/bin/sh

#

# tel - lookup BOKU phone numbers

#

# by Partl, BOKU Wien

# last change: Partl 92-11-13

#

# Usage:

# tel pattern

# prints all lines of $FILE that match the search pattern (name, part

# of name, number, part of number, regular expression as understood

# by egrep).

# $FILE contains the phone list (lastname, initial, extension).

FILE=/usr/local/lib/telefon.liste

if [ $# != 1 ]

then   echo "Usage: tel searchpattern" 1>&2

  exit 1

fi

egrep -i "$1" $FILE

exit

clean-tmp

#! /bin/sh

#

# clean-tmp   woechentliche Verkleinerung von /tmp

#     (Loeschen aller Files aelter als 1 Woche)

#

# last change   Partl 93-01-27

#

# Aufruf als User root, jeden Montag frueh mittels crontab.

for dir in /tmp /usr/tmp /opt/tmp /var/tmp

do

  if   [ -d $dir ]

  then   find $dir -type f -mtime +7 -exec rm {} \;

  fi

done

exit

vi-crontab

#! /bin/sh

#

# vi-crontab     Prozedur zum Veraendern der crontab mittels vi

#

# Autor:     Hubert Partl, BOKU Wien

# letzte Aenderung:   1992-03-08

# verwendete Files:

FILE=$HOME/crontab.copy

#       behaelt den neuen Zustand der crontab des Usernames

# alte crontab holen:

crontab -l >$FILE

# Aenderungen durchfuehren:

vi $FILE

# wird mit :wq oder :q! beendet

crontab <$FILE

exit

Beispiel für Optionen

newlogin

#!/bin/sh

#

# newlogin

#

# by Hubert Partl, BOKU Wien

# last change: Partl 1993-05-28

#

# Usage: newlogin [-help] [-host hostname] [-user username]

# Standard-Werte:

hostname='hostname'

username='logname'

help=false

okay=true

HELP="Help:

Newlogin startet eine neue Login-Session.

Bei Angabe des Parameters -host hostname

auf diesem Unix-Rechner, sonst am eigenen Rechner.

Bei Angabe des Parameters -user username

unter diesem Username, sonst unter dem eigenen Username.

Anschliessend wird man nach dem Passwort gefragt.

Bei Angabe des Parameters -help erhaelt man Erklaerungen

und es wird keine Session gestartet."

USAGE="Usage: newlogin [-help] [-host hostname] [-user username]"

# Optionen verarbeiten:

while [ $# -gt 0 ]

do   case "$1" in

  -help)   help=true

    shift;;

  -host)   shift

    if [ $# -eq 0 ]

    then   echo "Hostname fehlt."

      okay=false

    else   hostname="$1"

      shift

    fi;;

  -user)   shift

    if [ $# -eq 0 ]

    then   echo "Username fehlt."

      okay=false

    else   username="$1"

      shift

    fi;;

  *)   echo "Falscher Parameter $1"

    okay=false

    shift;;

  esac

done

# Ausfuehrung:

if $help

then   echo "$HELP"

elif $okay

then   echo "Login auf Rechner $hostname unter Username $username"

  rlogin $hostname -l $username

  exit   # with exit status of rlogin

else   echo "$USAGE"

  exit 1

fi

Beispiele für Anwendungen von awk und sed

DISPLAY-Variable

Aufgabe: Automatische Bestimmung des DISPLAY-Variablen für X-Window in der Form

DISPLAY=ipadresse:0       (Bourne-Shell)

export DISPLAY

bzw.

setenv DISPLAY ipadresse:0       (C-Shell)

An Stardent-Rechnern liefert   who am i

eine Zeile der Form       username tty monat tag zeit ipadresse

DISPLAY='who am i | awk '{print $6}'':0An HP-Rechnern liefert     who am i -R

eine Zeile der Form       username tty monat tag zeit (ipadresse)

DISPLAY='who am i -R | sed -e 's/^.*(//' -e 's/)/:0/''

tree

Aufgabe: Ausgabe der Directory-Hierarchie in der Form

directory
|---- file
|---- directory
| |---- directory
| | |---- file
| | |---- file
| |---- directory
| | |---- file
| | |---- file
|---- file
|---- file

Ablaufskizze:

find           subdir/subdir/subdir/file

1. sed-Schritt       |---- |---- |---- file

2. sed-Schritt       | | |---- file

Shell-Script:

#!/bin/sh

#

# tree     print directory tree

#

# by Hubert Partl, BOKU Wien

# using ideas from

#   Helmut Herold: "AWK und SED", Unix und seine Werkzeuge,

#   Addison Wesley 1991

#

# last change: Partl 1993-03-18

#

# Usage:

#   tree directory     or

#   tree       (current directory)

if [ $# -gt 1 ]

then   echo "Usage: tree [directory]"

  exit 1

fi

dir=${1-.}   # Default for directory is . (current directory)

if [ ! -d $dir ]

then   echo "$dir is no directory."

  exit 1

fi

find $dir -print | sort | \

  sed   -e 's:[^/]*/:|---- :g'     -e 's/---- |/ |/g'

#   first   replace   subdir/

#     by   |----

#     with   subdir = [not /]*

#   then           replace   |---- |----

#             by   | |----

exit

Beispiele für größere Shell-Scripts

getdotfiles

Grundlagen:

Jeder Benutzer hat in seinem Home-Directory Startup-Files mit Namen der Form ".xxx". Das EDV-Zentrum legt unter "/etc/d.xxx" Standard-Versionen dieser Startup-Files an.

Beim Anlegen eines neuen Username werden die Standard-Versionen in sein Home-Directory kopiert. Der Benutzer kann die Files dann nach seinen Bedürfnissen verändern.

Wenn das EDV-Zentrum später neue oder verbesserte /etc/d.xxx-Files zur Verfügung stellt, kann jeder Benutzer diese mit "getdotfiles" in sein Home-Directory kopieren. Seine alten Versionen werden dabei unter anderen Namen "gerettet", damit er die Neuerungen mit "diff" kontrollieren und seine eigenen Modifikationen nach Bedarf in die neuen Files einfügen kann.

Shell-Script:

#!/bin/sh

#

# getdotfiles     copy new default startup files into home directory

#

# by Bauer and Partl, BOKU Wien

# last change: Partl 93-01-07

#

# Source files:   /etc/d.xxxx

#       (usually symoblic links to /usr/local/etc/d.xxx)

# Destination files:   $HOME/.xxx

# Backup files:   $HOME/OLD.xxx

#

# For each .xxx in a predefined set (.login, .Xdefaults, .exrc etc.) do

# if source file exists

# if destination file exists already

# if destination file is different from source file

# copy old destination file to backup file

# copy source file to (new) destination file

# else do nothing

# fi

# else (destination file does not exist yet)

# copy source file to (new) destination file

# fi

# fi done

cd $HOME

cflag=false

for dfile in   .profile .login .cshrc .logout \

    .xsession .mwmrc .Xdefaults .vueprofile .Xdefaults.pc .xpc \

    .exrc .mailrc

do

  sfile=/etc/d$dfile

  if [ -r $sfile ]

  then   if [ -f $dfile ]

    then   if cmp -s $dfile $sfile

      then   echo "No changes in $dfile."

      else   cp $dfile OLD$dfile

        cp $sfile $dfile

        echo "Old $dfile saved as OLD$dfile, new $dfile copied."

        cflag=true

      fi

    else   # $dfile does not exist

      cp $sfile $dfile

      chmod 700 $dfile

      echo "New $dfile created."

      cflag=true

    fi

  fi

done

if $cflag

then   echo "New startup files may need a new login to become active."

fi

exit 0

Typische Ausgabe:

No changes in .profile.
Old .login saved as OLD.login, new .login copied.
Old .cshrc saved as OLD.cshrc, new .cshrc copied.
No changes in .logout.
New .Xdefaults created.
New .Xdefaults.pc created.
New .xpc created.
New startup files may need a new login to become active.

mailall

Grundlagen:

Mail-Adressen sind Usernames.

Institute sind Unix-Gruppen mit der Institutsbezeichnung (z.B. h999) als Groupname.

/etc/passwd enthält Zeilen der Form username:passwort:uid:gid:gecos:home:shell

/etc/group enthält Zeilen der Form   groupname:passwort:gid:kommentar

Ablauf-Skizze (ohne Fehler-Überprüfungen etc.):

file=$1
subject="$2"
gid='awk -F: '$1 == "h999" {print $3}' /etc/group'
adrlist='awk -F: '$4 == "'$gid'" {print $1}' /etc/passwd'
mail -s "$subject" $adrlist < $file

Komplettes Shell-Script:

#!/bin/sh

#

# mailall     Mail an alle Benutzer eines Instituts senden

#

# Autor:     Partl, BOKU Wien

# letzte Aenderung:   93-03-08# Aufruf:     mailall file [subject]# verwendete Files:

# /etc/group     fuer Zuordnung Institut (Groupname) -> GID

# /etc/passwd   fuer Zuordnung GID -> Usernames

inst=h999     # Institutsnummer = Unix-Groupname

defaultsubject="mailall-Message fuer $inst"

if   [ -x /usr/ucb/mail ]

then   mailprog=/usr/ucb/mail

elif   [ -x /usr/bin/mailx ]

then   mailprog=/usr/bin/mailx

elif   [ -x /bin/mail ]

then   mailprog=/bin/mail

else   echo "Kein Mail-Programm gefunden."

  exit 1

fi

if [ $# -lt 1 -o $# -gt 2 ]

then   echo "Usage: mailall file [subject]"

  exit 1

fi

file=$1

subject=${2-$defaultsubject}

if [ ! -r $file ]

then   echo "Ich kann File $file nicht lesen."

  exit 1

fi

if file $file | grep text >/dev/null

then   :   # okay

else   echo "$file ist kein Text-File."

  exit 1

fi

gid='awk -F: '$1 == "'$inst'" {print $3}' /etc/group'

if [ -z "$gid" ]

then   echo "Institut bzw. Unix-Gruppe $inst existiert nicht."

  exit 1

fi

adrlist='awk -F: '$4 == "'$gid'" {print $1}' /etc/passwd'

if [ -z "$adrlist" ]

then   echo "Es gibt keine 'hostname'-Benutzer in der Gruppe $inst."

  exit 1

fi

echo "Soll File $file mit Subject $subject"

echo "an $adrlist" | tr "\012" " "

echo ""

echo "gesendet werden? (Return fuer ja, Ctrl-c fuer nein)"

read antwort

$mailprog -s "$subject" $adrlist < $file

exit   # return exit status of $mailprog

Typische Ausgabe:

Soll File willkomm mit Subject Willkommen im Kurs
an kurs kurs1 kurs2 kurs3 kurs4 xkurs
gesendet werden? (Return fuer ja, Ctrl-c fuer nein)

Beispiele zur Optimierung

Textsuche in File-Hierarchie

schlecht:

find . -type f -exec grep text {} /dev/null ';'   (zu viele Prozesse)

grep text 'find . -type f -print'       (Gefahr von "command too long")

gut:

find . -type f -print | xargs grep text

Filegröße

falsch:

size='wc -c $file'           (wc liefert Länge und Filename)

wc -c $file | read size rest         (read liest nicht aus Pipe)

wc -c $file | ( read size rest )       (size nur in Subshell gesetzt)

size='wc -c $file | cut -d" " -f1'     (mehrere Blanks vor der ersten Ziffer)

richtig, aber nicht gut :

size='wc -c $file | awk '{print $1}''     (zu viele Prozesse)

size='ls -l $file | awk '{print $5}''       (zu viele Prozesse)

set 'wc -c $file'           (Parameter werden zerstört)

size=$1

set 'ls -l $file'           (Parameter werden zerstört)

size=$5

size='cat $file | wc -c'         (zu viele Prozesse)

gut:

size='wc -c <$file'


BOKU Wien - ZID - Handbücher - Copyright
Hubert Partl