Die Bash, das unbekannte Wesen

Builtins

Die Leistungsfähigkeit einer Shell ist nur zum Teil durch die komfortablen Möglichkeiten bei der interaktiven Bedienung begründet. Mindestens genauso wichtig sind die "inneren Werte" einer Shell, also die eingebauten Befehle, die Funktionen zur Bearbeitung von Variablen, Skripten und Funktionen sowie anderen Features.

Ein Überblick

In den letzten Teilen haben wir die Bash zum Start von Befehlen interaktiv eingesetzt. Durch Alias-Definitionen und Funktionen können quasi "on the fly" neue Befehle programmiert werden. Normalerweise werden neben diesen "Eigenkonstruktionen" und den externen Befehlen besonders gern die eingebauten Befehle einer Shell verwendet, weil diese ohne zusätzlich Verzögerung durch das Laden der Programmdatei gestartet werden können. Die Bash verfügt daher über ein wohl sortiertes Angebot an eingebauten Befehlen, mit denen viele Aufgaben schnell und unproblematisch gelöst werden können. Diese Eigenschaft haben übrigens viele Shells, besonders viele Befehle sind in der Sash (Stand alone Shell) vorhanden, mit der sogar tar-Archive bearbeitet werden können, ohne dass eine einzige Programmdatei zusätzlich geladen werden müsste. Diese Shell wird oft in "Rettungssystemen" eingesetzt.

Ein Hintergrund-Job entsteht durch das "Ablösen" eines Prozesses vom kontrollierenden Terminal. Normalerweise können einem beliebigen Programm von dem Terminal aus, von dem es gestartet wurde, Signale zugesendet werden. Ein einfaches Beispiel: ein lange andauernder sort-Befehl kann durch [Strg-C] beendet werden, wie die meisten anderen Befehle auch. Viele Programme können auch andere Signale empfangen und interpretieren. So wird etwa der init-Prozess (oder auch xinit) durch das SIGHUP-Signal (HUP: hang up "auflegen") veranlasst, seine Konfigurationsdatei erneut einzulesen. Viele weitere Programme (aber nicht unbedingt alle) reagieren so auf dieses Signal. Es wird mittels des kill-Befehls an die entsprechenden Prozesse gesendet.

Im folgenden sollen die wichtigsten eingebauten Befehle der Bash kurz vorgestellt werden. Sie können in folgende Gruppen eingeteilt werden:

Befehle zum

  • Steuern von Hintergrundprozessen (Job-Kontrolle): bg, fg, disown, jobs, kill (siehe Linux User Heft 12-2000)
  • Zugriff auf Verzeichnisse: cd, dirs, popd, pushd, pwd, (siehe unten)
  • Bearbeiten von Befehlszeile und History: fc, history (siehe Linux User Heft 8-2000)
  • Steuern der Programmausführung in Funktionen und Skripts: . (source), :, alias, unalias (siehe den vorigen Teil) , break, builtin, command, enable, eval, exec, exit, getopts, return, shift, trap, type, wait
  • Aufbau von Kontrollstrukturen: continue, test, [, for, if, elif, while, until, case, select (siehe unten)
  • Bereitstellen und zur Bearbeitung von Variablen und Funktionen: declare, typeset, export, local, readonly
  • Ein- und Ausgabe von Daten: echo, printf, read
  • diverse Funktionen: hash, help, let, logout, suspend, times

Ein großer Teil dieser eingebauten Befehle, etwa die zur Job-Kontrolle, der Arbeit mit Verzeichnissen und zur Bearbeitung von Befehlszeilen, wurde in den letzten Teilen dieser Reihe schon vorgestellt.

Die größten Gruppe von Befehlen dienen zur Programmierung in Funktionen (siehe den letzten Artikel) und Skripts bzw. zur Steuerung der Abläufe in diesen. Einige der unter Kontrollstrukturen aufgeführten Befehle sind streng genommen gar keine, sondern "reservierte Worte". Diese Unterscheidung spielt hier aber keine Rolle, zumal beide Typen in Funktionen und Skripts analog benutzt werden.

Skripts sind in externen Dateien gespeicherte Befehle. Um ein Skript auszuführen, kann dieses entweder direkt mit der Shell geladen werden ("bash script") oder in einer laufenden Shell mit dem source-Befehl eingelesen und dann ausgeführt werden: "source script". Da dieses Feature sehr oft benötigt wird, gibt es in der Bash für source die Abkürzung ".".

Mit command und builtin wird der Shell mitgeteilt, welche Variante eines Befehls verwendet werden soll, sofern mehrere Möglichkeiten zur Verfügung stehen. Der kill-Befehl dient zum Senden von Signalen an einen Prozess und ist sowohl als eingebauter Befehl als auch in Form eines externen Befehls unter Linux vorhanden. Ein typisches Beispiel für den Einsatz von builtin ist folgende Definition eines erweiterten cd-Befehls:

    
cd () { builtin cd $*; echo $PWD; }

In manchen Fällen muss unbedingt die externe Variante eines Befehls eingesetzt werden, auch wenn ein gleichnamiges Alias in der Shell definiert wurde oder es eine entsprechende Funktion gibt. In diesem Fall wird dies der Bash durch "command befehl" mitgeteilt. echo gibt den im Argument übergebenen Text aus. Dabei unterscheiden sich der interne und externe echo-Befehl oft in der Optionssyntax voneinander. Die Unterschiede können Sie sich folgendermaßen vergegenwärtigen:

    
$> help echo
 …
 $> info echo

Ziemlich umfangreiche Möglichkeiten bietet die Bash zur Konstruktion von Schleifen und Verzweigungen im Programmablauf durch die sogenannten Kontrollstrukturen. Die einfachste Möglichkeit bietet der test-Befehl, der abgekürzt auch als "[ Bedingung ]" formuliert werden darf.

Dem test-Befehl wird mittels einer Option die Bedingung mitgeteilt, anhand der ein Test durchgeführt werden soll. Im einfachsten Fall wird etwa die Existenz ("-e Datei") einer Datei oder eines Verzeichnisses geprüft, komplexere Bedingungen lassen sich aber ebenfalls formulieren:

    
test /usr/bin/latex -eh /usr/share/texmf/bin/tex

Diese Bedingung ist nur dann erfüllt, wenn das LaTeX-Programm unter /usr/bin/latex installiert und durch einen symbolischen Link auf /usr/share/texmf/bin/tex realisiert ist. Alle möglichen Testbedingungen können Sie auf deutsch in der Bash-Manpage nachlesen, die Online-Hilfe zeigt eine kurze Zusammenfassung:

    
$>
 help test
 test: test [expr]
 …
     Arithmetic binary operators return true if ARG1 is equal, not-equal,
     less-than, less-than-or-equal, greater-than, or greater-than-or-equal
     than ARG2.

Das Ergebnis eines Tests teilt die Bash übrigens durch den sogenannten "Rückgabewert" (Return Code) mit, der mittels if ausgewertet werden kann. Nur wenn dieser "0" (Null) ist, wurde die Bedingung wahrheitsgemäß erfüllt.

Ein einfaches Skript zeigt die Anwendung von if und die Kombination mit test bzw. [].

    
#!/bin/sh
 #
 if test $# = 0 -o "$1" = "-h" -o "$1" = "--help" ; then
   echo ' grepfind – durchsucht alle Dateien im und unterhalb des angegebenen Verzeichnisses mittels egrep '
 …
   echo ' Beispiel: grepfind /home "hello world" '
 else
   if [ "$2" = "" ]; then
     find . -type f -exec egrep -i "$1" /dev/null {}\; |sed -e 's/[^ -~][^ -~]*/ /g'
   else
     if [ -d "$1" ];then
       find $1 -type f -exec egrep -i "$2" /dev/null {}\; |sed -e 's/[^ -~][^ -~]*/ /g'
     else
       echo "Fehler: $1 ist kein Verzeichnis"
     fi
    fi
 fi

Dieses oder ein ähnliches Skript ist unter dem Namen grepfind auf vielen Linux-Systemen vorhanden. Es erlaubt dem User, mit einem für egrep (erweitertes grep) definierten Ausdruck die Dateien in vielen Verzeichnisse zu durchsuchen. egrep-selbst verfügt normalerweise nicht über diese Möglichkeit.

Wichtig sind beim Einsatz von if-Strukturen besonders zwei Punkte: Die Bedingung, also die Befehle, deren Rückgabewert geprüft werden, müssen mit einem Semikolon beendet werden und außerdem, dass jede mittels if angefangene Kontrollstruktur auch mit einem fi beendet wird.

Das Erfüllen der Bedingung führt zum Ausführen der nach dem Schlüsselwort then angegebenen Befehle. Eine Alternative wird durch else begonnen, eine alternative Abfrage kann auch mittels elif eingefügt werden. Eine Bedingung kann durch das Voranstellen eines Ausrufungszeichens negiert werden, Rückgabewerte ungleich Null werden dann als Null (und umgekehrt) interpretiert.

Alternativ zu if können noch vier weitere Kontrollstrukturen in Skripts verwendet werden, die wir nachfolgend besprechen.

Mit case werden mehrfache Verzweigungen realisiert, wie sie bei der Auswertung von Optionen oder in einfachen Menüs auftreten:

    
case $1 in
     -i|-iconic) iconic="-iconic" ; shift ;;
     -s|-secure) rsh="ssh" ;;
     -*)         usage ;;
     *)          hosts="$hosts $1" ; shift ;;
 esac

In diesem Beispiel wird durch $1 der erste Positionsparameter durch case ausgewertet. Die im dem durch $1 repräsentierten Parameter vorhandene Zeichenkette wird mit -i bzw. -iconic verglichen; bei Übereinstimmung wird die Befehlsgruppe bis zum abschließenden ;; ausgeführt. Wenn keine Übereinstimmung vorhanden war, werden die folgenden Zeilen analog bearbeitet. Tritt in dem Vergleichstext ein Sternchen auf, wie etwa in "-*" oder "*", dann interpretiert die Bash beliebige Texte als Treffer. Jeder Vergleichstext muss mit einer schließenden runden Klammer beendet werden, mehrere gleichwertige Texte können durch einen senkrechten Strich (dem Pipe-Zeichen) getrennt werden.

Mittels while können auf einfache Weise Schleifen erzeugt werden, die so lange ausgeführt werden, bis der letzte Befehl im Bedingungsblock einen Fehlercode (Rückgabewert ungleich Null) erzeugt.

    
while read i; do
   set $i
   file1="$2"
   while [ ! -z "$3" ]; do
     rm -f "$3"
     ln "$file1" "$3"
     shift
   done
 done

In diesem Beispiel (es handelt sich um den Skript-Befehl nodup von Oleg Kibirev zur Reduzierung von Festplattenplatzverbrauch durch doppelte Dateien) liest das Skript via read eine Zeile in die Variable i ein, die aus mindestens drei Teilen besteht, zum Beispiel:

    
1 ./sdd-1.22/RULES/rules.tag ./star-1.2/RULES/rules.tag

Als erstes wird die Größe von Files, die mehr als einmal in einem Verzeichnisbaum vorhanden sind, angegeben (in kByte), danach die Pfade und Namen zweier identischer Dateien. set $i wandelt die übergebene Befehlszeile so um, dass mit den normalen Positionsparametern auf die einzelnen Teile zugegriffen werden kann.

Die zuletzt angegebenen Dateien ("$3") können gelöscht und durch einen Link auf die Originaldatei ("$2") ersetzt werden. Der Befehl shift bewirkt dabei, dass alle Argumente um eine Position nach links verschoben werden. Aus dem zweiten Argument wird das erste, aus dem dritten das zweite, usw. So kann auf einfache Weise immer der dritte Positionsparameter verwendet werden, auch wenn mehr als drei Argumente vorhanden sind.

until stellt eine Variante von while dar. Befehle werden nur dann ausgeführt, solange der letzte Befehl im Bedingungsblock einen Rückgabewert ungleich Null ist. for ist die vierte Schleifenform, die in der Praxis sehr häufig verwendet wird. In dieser Schleife durchläuft eine Variable alle Elemente einer Liste, die beispielsweise aus einer Gruppe von Dateien besteht. Jedes Element dieser Liste wird dabei genau einmal und in der angegebenen Reihenfolge bearbeitet.

    
for Name [in Liste … ;] do Befehle; done

Name bezeichnet eine Variable, der der Reihe nach alle Werte aus der Liste zugewiesen werden. Auf diese Variable kann mit den Befehlen zugegriffen werden. Unterbleibt die Angabe der Liste, so wird implizit der spezielle Parameter $@ (expandiert zu allen Argumenten in der Befehlszeile, siehe den vorigen Artikel dieser Serie) eingesetzt. Oft wird für das Listenargument eine von der Bash ausgeführte Expandierung (beispielsweise mit Wildcards à la /etc/*) angegeben.

Das folgende Skript zeigt die Anwendung von test, case und for. Es ist unter dem Namen testlp in einigen Distributionen zu finden und ermittelt die konfigurierten und damit verfügbaren Druckerports.

    
#!/bin/sh -p
 test "$UID" != "0" && { echo "$0: Only root should call me."; exit 1; }
 testlp () {
     local lp="$1"
     set – `ls -l $lp`
     case "${5}${6}" in
         6,[012]) ( > $lp ) 2>&1 ;;
         *) { echo "$0: Wrong device file $lp (${5}${6})" 1>&2 ; exit 1; } ;;
     esac
 }
 for dev in /dev/lp? ; do
     result="`testlp $dev`"
     test -z "$result" && echo "$dev"
 done
 exit 0

Die Anwendung von testlp ist denkbar einfach. Der Systemverwalter (root-Account) ruft das Skript auf:

    
$# testlp
 /dev/lp0

Angezeigt wird die Schnittstelle, über die der Drucker angeschlossen werden kann.

Verzeichnisse

Der Wechsel zwischen mehreren Verzeichnissen gehört zu den etwas nervigen Arbeiten auf der Shell. Die Bash verfügt daher über eine Reihe von Möglichkeiten, diese Aktionen möglichst einfach zu gestalten.

Für den einmaligen Wechsel in ein neues Verzeichnis wird der eingebaute Befehl cd verwendet. Als Argument wird das Zielverzeichnis angegeben. Fehlt diese Angabe, wechselt die Shell in das Home-Verzeichnis des Anwenders. Nach einem Wechsel kann der Anwender jederzeit durch cd - in das vorherige Verzeichnis zurückspringen. Mehrere Verzeichnisse können in einem sogenannten "Directory-Stack" (Verzeichnis-Stapel) zwischengespeichert werden. Dazu stellt die Bash den Befehl pushd zur Verfügung. Mit dem Befehl kann in ein neues Verzeichnis gewechselt werden; gleichzeitig wird der Verzeichnispfad gespeichert. Mehrere Verzeichnispfade können so "übereinander" aufbewahrt werden. Zu einem späteren Zeitpunkt kann der Anwender dann mittels popd in umgekehrter Reihenfolge diese Verzeichnisse erneut auswählen. Zwei kleine Funktionen vereinfachen dies:

    
c () {
     pushd $*;
     ls $LS_OPTIONS
 }
 - () {
     popd;
     ls $LS_OPTIONS
 }

"c" stellt einen erweiterten cd-Befehl dar, da alle Verzeichnisse auf dem Directory-Stack vorgehalten werden. Außerdem wird der Inhalt des neuen Verzeichnisses automatisch via ls angezeigt. "-" baut dagegen den Directory-Stack wieder ab, die Shell wechselt also in das letzte Verzeichnis auf dem Stack. Auch hier wird wieder der Inhalt des aktuellen Verzeichnisses automatisch angezeigt. Beide Befehle arbeiten problemlos mit cd zusammen. Mit pwd wird das aktuelle Verzeichnis angezeigt, dirs (das "s" nicht vergessen!) listet den aktuellen Directory-Stack auf:

    
$> pwd
 /home/work/Linux-User/…
 $> c /tmp
 …
 $> dirs
 /tmp …
 $> -
 …

Zwei Anmerkungen noch dazu: Oft werden neue Verzeichnisse benötigt, in denen dann gearbeitet wird. Dazu kann die folgende Funktion verwendet werden:

    
# create a new directory and change to
 nd() {
         if [ $# = "1" ]; then
                 mkdir -p $1; cd $1
         else    echo "Usage: nd
"
         fi
 }

Das Verhalten der Bash kann durch Optionen angepasst werden. Eine Option steuert das Verhalten bei der Angabe von fehlerhaften Verzeichnisnamen für cd und pushd:

    
$> shopt -s cdspell
 $> pushd /tmp
 $> pwd
 /tmp
 $>

Zunächst wird die entsprechende Option (cdspell) mit shopt -s gesetzt. Anschließend korrigiert die Bash kleinere Fehler in Verzeichnisnamen automatisch. Wird shopt ohne ein Argument aufgerufen, so listet dieser Befehl die aktuellen Einstellungen auf:

    
shopt
 cdable_vars     off
 cdspell         on
 …
 sourcepath      on

Details zu den Optionen der Bash werden in der Man-Page beschrieben.

Der Autor

Karsten Günther ist Diplom-Geologe und arbeitet als freier TeX- und LaTeX-Setzer für verschiedene Verlage. Zur Zeit ist er überwiegend als Herausgeber und Autor von Linux- und (La)TeX-Büchern tätig.

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Bash-Kurs Teil 6
    Die elementaren Konfigurationsmöglichkeiten mit eingebauten Funktionen der Bash sind der Schwerpunkt dieses Teils. Im nächsten wird ein komfortables Tool zur Erzeugung von Konfigurationsdateien vorgestellt.
  • Einführung in die Bash-Programmierung
    Skripte sollen meist wiederkehrende oder lästige Arbeiten automatisieren. Die Standard-Shell Bash stellt Ihnen dazu eine ganze Reige von Funktionen bereit. Dieser Artikel erklärt, wie Sie ihre eigenen Shell-Skripte schreiben.
  • Bash-Konfiguration, die zweite
    In diesem zweiten Teil der Serie über die Konfiguration der Bash wird es noch einmal praktisch: Das Einstellen des Prompts, und die für die Fehlersuche so nützlichen Tracing-Modi werden beschrieben.
  • Bash-Skripte sind Programme
    Wer regelmäßig mit der Shell arbeitet, wird leicht zum Programmierer: Schreiben Sie mehrere Shell-Befehle in eine Textdatei und machen diese ausführbar, haben Sie schon Ihr erstes Shell-Skript entwickelt. Die Shell bietet als Programmiersprache aber noch viel mehr.
  • Kreislauf
    Schleifen ermöglichen das mehrmalige Abarbeiten von Anweisungen. Dabei kann das begrenzende Ereignis sowohl außerhalb als auch innerhalb der Schleife liegen.
Kommentare

Infos zur Publikation

LU 11/2017: Server für Daheim

Digitale Ausgabe: Preis € 8,50
(inkl. 19% MwSt.)

LinuxUser erscheint monatlich und kostet 5,95 Euro (mit DVD 8,50 Euro). Weitere Infos zum Heft finden Sie auf der Homepage.

Das Jahresabo kostet ab 86,70 Euro. Details dazu finden Sie im Computec-Shop. Im Probeabo erhalten Sie zudem drei Ausgaben zum reduzierten Preis.

Bei Google Play finden Sie digitale Ausgaben für Tablet & Smartphone.

HINWEIS ZU PAYPAL: Die Zahlung ist ohne eigenes Paypal-Konto ganz einfach per Kreditkarte oder Lastschrift möglich!

Stellenmarkt

Aktuelle Fragen

Lieber Linux oder Windows- Betriebssystem?
Sina Kaul, 13.10.2017 16:17, 3 Antworten
Hallo, bis jetzt hatte ich immer nur mit
IT-Kurse
Alice Trader, 26.09.2017 11:35, 2 Antworten
Hallo liebe Community, ich brauche Hilfe und bin sehr verzweifelt. Ih bin noch sehr neu in eure...
Backup mit KUP unter Suse 42.3
Horst Schwarz, 24.09.2017 13:16, 3 Antworten
Ich möchte auch wieder unter Suse 42.3 mit Kup meine Backup durchführen. Eine Installationsmöglic...
kein foto, etc. upload möglich, wo liegt mein fehler?
kerstin brums, 17.09.2017 22:08, 5 Antworten
moin, zum erstellen einer einfachen wordpress website kann ich keine fotos uploaden. vom rechne...
Arch Linux Netzwerkkonfigurationen
Franziska Schley, 15.09.2017 18:04, 0 Antworten
Moin liebe Linux community, ich habe momentan Probleme mit der Einstellung des Lan/Wlan in Arc...