Was machen wir heute? 1) Einleitung - Grundlagen - Was ist eine Shell? - Batch-Verarbeitung vs. Ablaufsteuerung - Variablen - freie - reservierte (builtin) - Environment 2) Kontrollstrukturen + Funktionen - Grundlagen - if - case - while/until ... break/continue - for 3) File Descriptors - Streams - Grundlagen - STDIN / STDOUT /STDERR - Redirection - Pipe 4) Tools - The Power of Unix - builtin: echo, ls, export, trap ... - externe Programme: cat, grep, awk, sed, sort, expr logger, mail, tr, cut ... 5) Ressourcen Grundlagen: Was ist eine Shell? - Shell ist ein Interpreter, der Kommandos entsprechend seiner eigenen Syntax interaktiv oder selbständig ausführt. - Shell ist eine Umgebung ("Muschel"), in der Programme ablaufen. Jede Shell hat ihren eigenen Satz Variablen, File-Descriptoren usw. - Shell ist die Schnittstelle zwischen Benutzer und Betriebssystem. - Es gibt viele verschiedene Shells, von denen jede ihre eigene Syntax hat. Einige Beispiele: - sh: Bourne Shell Die Mutter aller Shells - csh: C-Shell Shell mit C-ähnlicher Syntax - ksh: Korn Shell Mächtige, C-orientierte Shell. Solaris! - tcsh: Ext. C-Shell Erweiterte, komfortable C-Shell - bash: Bourne Again SH Erweiterte, komfortable Bourne Shell - Eine Shell ist immer ein guter Anlass für einen Glaubenskrieg! - Und MERKE: Ein Shell-Script muss immer ausführbar sein! # chmod a+x Grundlagen: Batch-Verarbeitung vs. Ablaufsteuerung BATCH - Batch(Stapel)-Programme führen einfach eine Reihe von Programmen in fester, unveränderbarer Reihenfolge aus. # command1 # command2 # command3 usw. - Es erfolgt keine Steuerung des Ablaufes. - Es werden einfachste Interpreter verwendet (command.com), im einfachsten Fall reicht ein Loader. ABLAUF - Es wird immer noch eine Reihe von Programmen ausgeführt. - Der Ablauf ist aber von einer Reihe von Ereignissen abhängig, und nicht jedes Programm kommt notwendigerweise zur Ausführung. # if (command1 was successful) # then run command2 # else run command3 - Es können Benutzer-Interaktionen ausgewertet werden. - Es kommen komplexe Interpreter zum Einsatz. Shell-Scripts können sowohl als Batch- wie auch als ablaufgesteuerte Programme vorkommen. Grundlagen: VARIABLEN - Variablen sind Platzhalter für Werte. Man kann ihnen also Werte zuweisen, und diese später wieder abfragen. Dazu wird unterschieden: VAR: Name der Variablen $VAR: Wert der Variablen # VAR1=1 # VAR2=2 # /wir/tun/irgendwas/anderes # echo \$VAR1: $VAR1 '$VAR2:' $VAR2 $VAR1: 1 $VAR2: 2 Der Wert kann aber auch aus einem andern Programm kommen: (Hier z.B. 'echo') # VAR3=`echo ${VAR1}${VAR2}` # echo \$VAR3: $VAR3 $VAR3: 12 (Hier z.B. 'hostname') # HOSTNAME=`hostname` # echo $HOSTNAME purely Der Shell muss mitgeteilt werden, dass sie zuerst den Teil rechts vom "=" ausführen soll. Dies geschieht mit "Backquotes": `command`. # HOSTNAME=hostname # echo $HOSTNAME hostname - Variablen haben einen Typ. Shell-Variablen sind grundsätzlich vom Typ "String". # VAR1=1 # VAR2=$VAR1+1 # echo \$VAR1: $VAR1 '$VAR2:' $VAR2 $VAR1: 1 $VAR2: 1+1 # Mathematische Operationen müssen von speziellen Programmen übernommen werden: (Achtung Backquotes!) # VAR1=1 # VAR2=`expr $VAR1 + 1` # echo \$VAR1: $VAR1 '$VAR2:' $VAR2 $VAR1: 1 $VAR2: 2 # - Variablen-Namen sind grundsätzlich frei (mit ein paar Ausnahmen) # MEINE_VIEL_ZU_LANGE_UND_UNUEBERSICHTLICHE_VARIABLE=1 # A="Endlich eine kurze Variable" - Quotes: Die Shell interpretiert verschiedene Quote-Zeichen anders: # VAR1="Variable" # echo "$VAR1" '$VAR1' `$VAR1` bash: Variable: command not found Variable $VAR1 - Reservierte Variablen (Shell-abhängig) $0 - Name des laufenden Programms $1 - 1. Argument (argv[1]) $2 - 2. Argument (argv[2]) ... $n $$ - Prozessnummer (PID) des laufenden Programms $@ - Alle übergebenen Argumente (Command Line) $* - -"- $# - Anzahl der Argumente $? - Exit-Status (Rückgabewert) des zuletzt ausgeführten Kommandos - Ein erstes Beispiel-Script: Ausgabe reservierter Variablen #!/bin/bash <-- Sagt dem Betriebssystem, was es mit dem File machen soll # builtin.sh <-- Kommentar # display some builtin variables echo echo "\$0: $0" echo "\$1: $1" echo "\$2: $2" echo "\$@: $@" echo "\$*: $*" echo "\$#: $#" echo echo "shift" shift echo "\$1: $1" echo "\$2: $2" echo "\$@: $@" echo "\$*: $*" echo "\$#: $#" echo "\$$: $$" echo echo "\$?: $?" bla echo "\$?: $?" echo - Das Betriebssystem (oder eine andere aufrufende Instanz, z.B. Webserver) stellt spezielle Variablen zur Verfügung, auf die immer zugegriffen werden kann: die Umgebungsvariablen (Environment) z.B.: $HOME /home/lugbe $PATH /home/lugbe/bin:/usr/local/bin:/usr/bin:/usr/X11R6/bin:/bin:/usr/lib/java/bin: /usr/games/bin:/usr/games:/opt/gnome/bin:/opt/kde2/bin:/opt/kde/bin:/opt/office52/program: $SHELL /bin/bash $DISPLAY :0 $USER: lugbe Das Kommando "env" gibt alle gerade vorhandenen Environment-Variablen aus. Das Kommando "export" fügt eine Variable dem Environment hinzu. # export MYVAR="meine Variable ist sehr schön!" Das Kommando "eval" (bei Zuweisungen) wertet zuerst den Teil links vom "=" aus, und führt dann das ganze Kommando noch einmal aus. # var=VAR # eval $var=12 # echo '$VAR': $VAR $VAR: 12 Script: eval.sh Kontrollstrukturen: GRUNDLAGEN - Kontrollstrukturen steuern wie gesagt den Ablauf eines Programms. Sie kommen in jeder Programmiersprache vor und machen den eigentlichen Kern der meisten Algorithmen aus. Sie prüfen eine Bedingung und führen aufgrund des Ergebnisses eine Aktion aus. Die wichtigsten Arten sind die "konditionalen" und "iterativen" Kontrollstrukturen. Beispiele: - konditional: # if # case - iterativ: # while # for Scripts: if.sh case.sh while.sh for.sh Kontrollstrukturen: Funktionen - Funktionen sind Code-Blöcke, die einmal geschrieben, aber beliebig oft ausgeführt werden. Sie müssen der Shell explizit als solche bekannt gemacht werden. - Funktionen können Parameter übergeben werden - genau wie unabhängigen Shell-Scripts. #! /bin/bash ### function code myadd() { tmp=0 args=$@ for i in $args do tmp=`expr $tmp + $i` done return $tmp } ### main code myadd 1 2 3 $VAR RES=$? myadd $RES 5 6 $VAR2 RES=$? usw() FILEDESCRIPTORS: I/O - Jedes von einem Programm geöffnete Objekt (Datei, Stream, Socket usw.) bekommt vom Betriebssystem eine eindeutige Nummer ("Handle"). Drei spezielle Handles sind genormt: - Standard Input: STDIN (0) - Standard Output: STDOUT (1) - Standard Error Output: STDERR (2) Bei interaktiv ablaufenden Programmen entspricht STDIN der Tastatur, STDOUT und STDERR dem Bildschirm (Terminal, tty). Bei nicht-interaktiven Programmen (d.h. kein tty), wird STDIN geöffnet, ohne an ein bestimmtes Terminal gebunden zu sein. Jedes andere Programm kann jetzt auf STDIN schreiben. So ist es möglich, einem Programm beliebigen Input zu liefern. Die häufigsten Anwendungen für diese Funktion sind - Pipes - Redirects FILEDESCRIPTORS: Pipes - Pipes ("Pfeifen") sind Verbindungsstücke zwischen zwei Programmen. Die Programme tauschen über sie Daten aus. Pipes werden vom Betriebssystem zur Verfügung gestellt als Mechanismus der Kommunikation zwischen Prozessen (Inter-process communication IPC). # PIPE | # VAR="Hello World" # echo $VAR | sed -e s/"World"/"LugBE"/g Hello LugBE # Was läuft hier genau ab? - Das Programm "echo" gibt "Hello World" auf seinen STDOUT aus. - Dort wartet aber gemeinerweise eine Pipe, die den Output entgegennimmt und an den STDIN des Programmes "sed" weiterleitet. - "sed" nimmt die Daten entgegen und verarbeitet sie entsprechend unseren Angaben -e s/"World"/"LugBE"/g (d.h. ersetze alle Vorkommen von 'World' durch 'LugBE') - Das Ergebnis schickt "sed" an seinen STDOUT weiter. - Dieser Mechanismus kann beliebig oft hintereinander angewendet werden ... und das wird er auch. # cat input.txt \ | sort -r +1 \ | tr -d ":" \ | awk '{print $4": " $3 " " $2 " " $1}' \ | tr [:upper:] [:lower:] \ | cat - FILEDESCRIPTORS: Redirection - STDIN, STDOUT und STDERR können aber auch beliebig umgelenkt werden. Dies geschieht mittel Redirections: # STDOUT > # STDIN < z.B. # echo "Hello LugBE" > hello.txt <-- STDOUT umlenken # sed -e s/"LugBE"/"World"/g < hello.txt <-- STDIN umlenken (identisch mit cat hello.txt | sed -e s/"LugBE"/"World"/g) # cat hallo.ttx 2> /dev/null <-- STDERR umlenken Es geht aber auch noch: # ping -c5 some.host.ch 2> ping-error.txt 1> ping-success.txt STDERR (Fehlermeldungen) geht nach ping-error.txt, normaler Programm- Output (STDOUT) nach ping-success.txt. Was dabei auf welchen Handle geschickt wird, entscheidet das Programm selbst (hier ping). # ping -c5 some.host.ch > ping-kombi.txt 2>&1 Das letzte Beispiel leitet STDERR an die Adresse von STDOUT ("&1") um, das wiederum in die Datei ping-kombi.txt umgeleitet wird. Am häufigsten sieht man: # /irgendein/kommando > /dev/null 2>&1 <-- who cares for errors? TOOLS: Uebersicht - "The Power of Unix" basiert auf einer Unzahl kleiner Helfer und Programme/Kommandos, die frei kombiniert werden können, um ein bestimmtes Problem zu lösen. Den Anwendungsgebieten sind dabei keine Grenzen gesetzt. Einige der häufigsten Tools haben wir bereits kennengelernt: - echo gibt einen String auf STDOUT aus - cat gibt eine Datei auf STDOUT aus - expr führt mathematische Operationen aus - sed verarbeitet Input-Streams nach dynamischen Regeln - sort sortiert Input-Streams zeilenweise - test prüft eine Bedingung - tr ersetzt einzelne Zeichen durch andere Einige andere sind z.B. - grep durchsucht Input-Streams nach bestimmten Zeichen (Regular Expressions) - cut zerlegt Input-Streams in Einzelteile - diff zeigt Unterschiede zwischen 2 Dateien an - find sucht Dateien mit bestimmten Merkmalen - logger loggt eine Nachricht via Syslog - sleep hält das Programm für eine bestimmte Zeit an - strings zeigt alle druckbaren Zeichen in einerd Datei an - touch "berührt"/erzeugt eine Datei - uniq filtert mehrfach vorkommende Zeilen (oft mit sort) - wc zählt Zeichen, Worte, Zeilen im Input - awk, perl MegaMonsterAlleskönner (TM) TOOLS im einzelnen - grep Durchsucht Input-Streams nach bestimmten Zeichen (Regular Expressions) # ifconfig | grep eth0 # grep -i FIVE input.txt - cut Zerlegt Input-Streams in ihre Einzelteile, gibt nur bestimmte Teile aus. # echo "Hello LugBE" | cut -d "g" -f2 # PID=`ps ax | grep -v "grep" | grep inetd | tr -s " " | cut -d " " -f2 - diff Zeigt Unterschiede zwischen 2 Dateien an # if diff file1 file2 >/dev/null 2>&1; then # echo "Dateien sind unterschiedlich" # shutdown -h now # fi - find Sucht Dateien nach bestimmten Kriterien # find /home/lugbe -type f -name einleitung\* -exec touch {} \; - logger Loggt eine Nachricht via Syslog # logger -p warn -t "manual logging" "Wie sag ich's bloss dem Sysadmin?" - sleep Hält das laufende Programm für eine bestimmte Zeit an # CNT=1 # while [ $CNT -le 5 ] # do # echo -n "$CNT " # CNT=`expr $CNT + 1" # sleep 1 # done - touch "Berührt" eine Datei - d.h. setzt ihre Access Time und Modified Time - oder erzeugt sie. # LOCKFILE=/var/run/myprog.lck # [ -f $LOCKFILE ] && exit 1 # touch $LOCKFILE - sort Sortiert Input-Streams zeilenweise lexikalisch oder numerisch # tail -20 /var/log/messages | sort -r -g -k1,2 - uniq Fasst identische Zeilen in Input-Streams zu einer zusammen. # cat file1 file2 file3 | sort | uniq RESSOURCEN - Es gibt unendlich viele Bücher zum Thema Shell-Scripting. Ich empfehle Grundlegendes: - UNIX Power Tools Jerry D. Peek, Tim O'Reilly, Mike Loukides O'Reilly - Linux in a Nutshell Ellen Siever, Stephen Spainhour, Stephen Figgins, Jessica Hekman O'Reilly Diese beiden enthalten Kapitel zu den wichtigsten Tools/Kommandos/Scriptsprachen. - Dein bester Freund: die Manpage (RTFM) # man bash # man - Dein zweitbester Freund: dein bester Freund Finde jemanden, von dem du glaubst, er kennt sich aus (besser noch: der das selber von sich meint). Ziehe am besten bei ihm ein. - Die Welt ist voll von Gurus. Man muss sie nur finden. z.B. lugbe@lugbe.ch, linux@lugs.ch HAPPY SCRIPTING - Na dann noch viel Spass beim Hacken. Happy trails.