5. Shellprogrammierung (/bin/sh)

Inhalt:

5.1 Ausführen von Programmen

5.2 /bin/sh ist (leider) nicht alles

5.3 Shell-Startup: Dot-files

Wenn die Shell gestartet wir werden eine oder mehrere Dateien gesucht und in ihnen enthaltene Befehle abgearbeitet, die sog. Dot-Files (so benannt weil die Namen mit einem Punkt anfangen, "versteckte" Dateien):

Hinweis: An der FH Regensburg liegen Kopien der Dot-Files in /home3/bedienst/dummy.

5.4 Aufgaben der Shell

5.5 Ein-/Ausgabeumlenkung

5.6 Wildcards

5.7 Variablen I

5.7.1 Allgemeines

5.7.2 Vorbesetzte Umgebungsvariablen

Ein Satz von Umgebungsvariablen sind unter Unix immer gesetzt, weitere können (und werden) von einzelnen Programmen ausgewertet:

5.7.3 Von der Shell vorbesetzte Variablen

Fortsetzung folgt, siehe "Variablen II"

5.8 Anführungszeichen

In der (Bourne) Shell existieren drei Arten von Anführungszeichen. Sie dienen dazu die Shell davon abzuhalten, bestimmte Expansionen automatisch vorzunehmen (Wildcards, Variablen, ...) bzw. um Argumente, Wildcards und Variable zu schützen:

` (Backtick):
Enthaltener String wird als Befehl ausgeführt und Ausgabe (stdout) wird eingesetzt:

Weitere Anmerkungen:
" (double Quote):
' (Tick, single Quote):
\ (Backslash):
Der Backslash hat nichts mit Anführungszeichen zu tun, da er jedoch (auch) die Shell davon abhält, Zeichen besonders zu interpretieren kann er benutzt werden um die Besondere Bedeutung mancher konstrukte aufzuheben:

5.9 Variablen II

5.10 Beispiele

5.10.1 Zählen in der Shell

5.10.2 Diskussion: Datei "-x" entfernen

5.11 Kontrollstrukturen

5.12 Shell Funktionen


Syntax: name() { befehle ; }
Anstatt des ";" kann auch ein Zeilenumbruch erfolgen!

Parameter werden in den positionalen Argumenten $1, ... übergeben, $#, $@ und $* sind entsprechend gesetzt.

Beispiel 1: "dir"-Befehl für interaktive Shells

Beispiel 2: Aufruf von "myls" (s.o.) auch ohne Argumente

Beispiel 3: Geschwindigkeitsvergleich

5.13 Weitere Shell-interne Befehle

Gründe für Shell-interne Befehle:
  1. Oft nicht als externer Prozess realisierbar, da Daten nicht vom Kind- an den Elternprozess zurueckgegeben werden können, z.b. cd
  2. Schneller:
    rfhpc8317% time sh -c 'count=0; while [ $count -lt 1000 ]; do \
    echo $count ;           count=`expr $count + 1` ; done' > /dev/null
    2.33u 8.59s 0:15.08 72.4%
    
    rfhpc8317% time sh -c 'count=0; while [ $count -lt 1000 ]; do \
    echo $count ; pwd;      count=`expr $count + 1` ; done' > /dev/null
    2.31u 9.01s 0:15.23 74.3%
    
    rfhpc8317% time sh -c 'count=0; while [ $count -lt 1000 ]; do \
    echo $count ; /bin/pwd; count=`expr $count + 1` ; done' > /dev/null
    4.16u 16.58s 0:29.15 71.1% 
cd verzeichnis
Macht das angegebene Verzeichnis zum Arbeitsverzeichnis der aktuellen Shell und von fortan gestarteten Prozessen. (Change Directory)

pwd
Gibt das Arbeitsverzeichnis der aktuellen Shell aus (Print Working Directory)

Beispiel:

$ pwd
/net/rfhs8012/home3/bedienst/feyrer
$
$ owd=`pwd`
$ cd /tmp
$ pwd
/tmp
$ cd $owd
$ pwd
/net/rfhs8012/home3/bedienst/feyrer
exit rc
Beendet die aktuelle Shell und gibt rc als Return-Code zurück.

Beispiel:

$ cat scr
exit $1
$ sh scr 17
$ echo $?
17 
exec befehl argumente
Aktuellen Shell-Prozess mit neuem Binary befehl überlagern und dieses Ausführen.

Beispiel:

$ echo $$
9832
$ sh
$ echo $$
9853
$ exec date
Mon Nov 26 02:55:42 MET 2001
$ echo $$
9832
$  
( befehle )
Die angegebenen Befehle werden in einer Subshell ausgeführt. Es wird kein eigener Prozess gestartet, in der Subshell gesetzte Variablen, geänderte Verzeichnisse etc. beeinflussen die umgebende Shell nicht.

Beispiel:

$ echo $$
9832
$ ( echo $$ )
9832                    # Gleiche PID - keine fork()
$ 

$ echo $SHELL
/soft/bin/tcsh
$ ( SHELL=foo ; echo $SHELL )
foo
$ echo $SHELL
/soft/bin/tcsh          # SHELL nicht verändert
$ 

$ pwd
/net/rfhs8012/home3/bedienst/feyrer
$ ( cd /tmp ; pwd )
/tmp
$ pwd
/net/rfhs8012/home3/bedienst/feyrer
$

$ ( date ; ls /tmp ) >x
$ cat x
Thu Apr 15 12:24:55 CEST 2004
kde-feyrer      ksmHUaafb       ksocket-feyrer  mcop-feyrer     uscreens

$ cd quelle
$ tar -cf - . \
> | ( cd ziel ; tar -xf - ) 
read varname
Liest Zeichenkette von stdin in Variable varname ein
Beispiel:

$ cat scr
echo Eingabe: \\c         # SysV; BSD: echo -n Eingabe:
read x
echo Ihre Eingabe: $x
$ sh scr
Eingabe: test
Ihre Eingabe: test




$ cat scr
#!/bin/sh
PATH=/usr/xpg4/bin:${PATH}     # besserer tail(1) auf Solaris
sum=0;
ls -l \
| tail -n +2 \
| while read line
  do
      set -- $line
      sum=`expr $sum + $5`
  done
  
echo Disk usage: $sum bytes
rfhpc8317% sh scr
Disk usage: 0 bytes
rfhpc8317% /usr/xpg4/bin/sh scr
Disk usage: 2927254 bytes
rfhpc8317% bash scr
Disk usage: 0 bytes 
Der 'tail'-Befehl dient zum Überspringen der "total"-Zeile am Anfang. Die Unterschiede der Ausgabe ergeben sich durch unterschiedliche Ansichten in welcher Umgebung die Befehle in Pipelines ausgeführt werden: 2.12 "Shell Execution Environment" des IEEE Std 1003.1 (POSIX), 2004 Edition legt fest dass ``each command of a multi-command pipeline is in a subshell environment; as an extension, however, any or all commands in a pipeline may be executed in the current environment.''. Eine Mögliche Loesung ist, while- und echo-Befehl zusammen in eine eigene Subshell "(...)" am Ende der Pipeline zu setzen.

eval string
Führt string als Befehl innerhalb der aktuellen Shell aus.

Beispiel:

$ cat scr
 echo "Variable: \c" ; read var
 echo "Wert: \c" ;     read val
 eval "alt=\$$var" ; echo Alter Wert von $var: $alt
 eval "$var=$val"
 eval "neu=\$$var" ; echo Neuer Wert von $var: $neu
$ sh scr
Variable: SHELL
Wert: test
Alter Wert von SHELL: /soft/bin/tcsh
Neuer Wert von SHELL: test 
. script
Arbeitet das angegebene Script im Kontext der aktuellen Shell ab, d.h. es kann auf lokale Variablen zugegriffen und diese verändert werden. ((t)csh: source).

Beispiel:

$ cat scr
foo=bar
$ echo $foo

$ sh scr
$ echo $foo

$ . ./scr
$ echo $foo
bar
Abhängig von der Implementierung ist, ob das angegebene Script im aktuellen Verzeichnis (relativ zu ".") oder im Suchpfad gesucht wird. Ist ersteres beabsichtigt, so ist dies explizit - wie im Beispiel - anzugeben!

wait
Wartet auf Ende von mit & gestarteten Kind-Prozessen.

Beispiel:

$ cat scr
sleep 5 &
date
wait
date
$ sh scr
Mon Nov 26 03:19:39 MET 2001
Mon Nov 26 03:19:44 MET 2001
$ 
time befehl
Führt den angegebene Befehl aus und zeigt am Ende an, wieviel Zeit für die Ausführung benötigt wurde.
Beispiel:
$ time sleep 5

real        5.0
user        0.0
sys         0.0
$ time date
Mon Nov 26 03:21:54 MET 2001

real        0.0
user        0.0
sys         0.0
$ 
which, type
Listen auf in welchem Verzeichnis sich ein bestimmtes Programm (Iim $PATH) befinde.

which -> (t)csh builtin oder externes Programm

type -> (ba)sh builtin Hinweis zur Optimierung:
trap
Signale abfangen, und ggf. Signal-Handler setzen.
rfhpc8317% cat /tmp/x
#!/bin/sh

trap 'echo INTERRUPT!' TERM INT
echo "^C abgestellt"
for i in 1 2 3 4 5 ; do echo ignoriert $i ; sleep 1 ; done
echo ""

trap TERM INT
echo "^C angestellt"
for i in 1 2 3 4 5 ; do echo unterbrechbar $i ; sleep 1 ; done

rfhpc8317% sh /tmp/x
^C abgestellt
ignoriert 1
ignoriert 2
ignoriert 3
^CINTERRUPT!
ignoriert 4
^CINTERRUPT!
ignoriert 5

^C angestellt
unterbrechbar 1
unterbrechbar 2
^Crfhpc8317% 

Literatur

(Übung)
(c) Copyright 1998-2006 Hubert Feyrer <hubert@feyrer.de>
$Id: 05-shellprogrammierung.html,v 1.31 2006/07/05 11:02:31 feyrer Exp $