Bash Shell

Freitag, 15. Oktober 2021

Fehler beim Ausführen von Shellscripts mit set beeinflussen

set -x Ausgabe des aktuell ausgeführten Kommandos innerhalb des Skripts
set -u Skript abbrechen, falls eine verwendete Variable nicht deklariert ist
set -e Skript abbrechen, falls ein Kommando innerhalb des Scripts mit Fehler beendet wurde
set -o pipefail Stellt sicher, das bei einen Fehler innerhalb einer Pipe einen Exitstatus != 0 zurück gegeben wird

.

Wichtige Builtin

shift [n] rückt die aktuellen Parameter ("$1" usw.) um "n" weiter
. ./script liest script ein und führt deren Zeilen in der aktuellen Shell aus (wichtig, um die Variablen der aktuellen Shell beeinflussen zu können)
exit ![n] beendet das Skript und gibt den Exit-Code "n" (bzw. 0) zurück
eval STRING wertet STRING aus und führt die resultierende Zeichenkette aus

.

Das Kommando trap

Mit trap kann auf Signale reagiert werden, um so z.B. den Programmabbruch zu verhindern.

Die Syntax lautet:

trap <action> <Signal>

  • <aktion> Ein beliebiges Kommando in einfachen Anführungsstrichen
  • <signal> Das Signal (Name oder Zahl) , das abgefangen werden soll. Eine Sonderrolle hat 0 als Skriptende.

Beispiel

#!/bin/bash
trap 'echo SIGINT erhalten' 2
i=0
while [ $i -lt 5 ]
do
   echo "Bitte nicht stören!"
   sleep 2
   i=`expr $i + 1`
done

In diesem Beispiel reagiert das Skript auf <crtl><c> mit der Meldung "SIGINT erhalten" - läuft aber weiter.
.

Das Kommando Test

Mit test kann die Ausführung von Kommandos ähnlich wie mit if/else Konstrukten gesteuert werden.

[ -f /tmp/sample.txt ] && {
  rm /tmp/sample.txt
} || {
  echo Datei /tmp/sample.txt nicht vorhanden
}

Mit dem Builtin Konstrukt [[ ]] und dem Operator =~ erlaubt die Bash Pattern Matching:
  [[ $VAR =~ ^[0-9]*$ ]] && echo "is integer"

.

Eingabe-/Ausgabekanäle umleiten

Die 3 standard Ein- und Ausgabekanäle unter Linux

  • stdin (Kanal 0)
  • stdout (Kanal 1)
  • stderr (Kanal 2)

Standard-Ausgabe in DATEI schreiben

echo "Zeile 1"  > sample.txt        # Default is stdout, deshalb nicht angegeben
echo "Zeile 1"  1> sample.txt       # Dito, Kanal explizit angegeben

Standard-Ausgabe an DATEI anhängen

echo "Zeile 2"  >> sample.txt

Fehler-Ausgabe in DATEI schreiben

myscript.sh 2>/dev/null

Standard- und Fehlerausgabe in DATEI schreiben

myscript.sh &>/dev/null
myscript.sh >/dev/null 2>&1         # Dito

Standard-Eingabe aus DATEI lesen

wc -l < sample.txt

String in die Standard-Eingabe schreiben (Here string)

bc <<< 5*4                          # wie echo '5*4' | bc

Kanäle von Befehlsfolgen umleiten

(echo bar;echo foo)>/dev/null 2>&1  # keine Ausgabe

HERE Dokumente

Here Dokumente schreiben den Text zwischen dem Trenner (hier EOF) an stdin eines Kommandos (hier cat).

VAR="one"
cat << EOF
$VAR two three
four five
EOF                                 # Variable $VAR wird aufgelöst

Sollen die Variablen-Substitution unterdrückt werden, muß der Trenner in einfachen Anführungszeichen gesetzt werden.

VAR="one"
cat << 'EOF'
$VAR two three
four five
EOF                                 # Variable $VAR wird nicht aufgelöst (Ausgabe $VAR)

Im folgenden Beispiel wird die Ausgabe von cat in eine Datei umgeleitet

cat << EOF > /tmp/out.file
one two three
four five
EOF

Process Substitution

wc < <(echo bar;echo foo)
(echo foo;echo bar) | wc            # Ausgabe wie oben

Befehls-Verkettungen

|| ODER - Führt das folgende Kommando nur aus, wenn das erste nicht mit Fehlercode "0" beendet wurde
&& UND - Nur, falls das erste Kommando erfolgreich war. Die Kommandos werden nacheinander ausgeführt.
& Das Programm wird im Hintergrund ausgeführt
{BEFEHLE} Führt die Befehle in der aktuellen Shell aus (Vorsicht - falls durch eine UND-/ODER-Verknüpfung die schließende Klammer übersprungen wird, führt das zu irritierenden Fehlermeldungen)
(BEFEHLE) Führt die Befehle in einer neuen Shell aus (also werden keine Variablen der aktuellen Shell verändert usw.)

.

Anführungsstriche

" doppelte Variablen und rückwärtige Anführungsstriche (Backticks) werden aufgelöst
' einfache Beides wird nicht aufgelöst
` rückwärtige Der Text wird als Kommando interpretiert und liefert dessen Ausgabe zurück

.

Strukturen

while

while BEDINGUNG
  do
    BEFEHL
    BEFEHL
  done

BEDINGUNG ist dabei i.d.R irgendein Programm, das entweder den Exit-Code "0" (=wahr) oder eine einen beliebigen anderen Exit-Code (=falsch) zurückliefert - häufig ist dies das Programm "test"

until

Siehe "while" - einfach "until" stattdessen einsetzen.

for

 for 1 2 3
 do
   BEFEHL
   BEFEHL
 done

 for ((i=1; i<10; i++))
 do
   BEFEHL
   BEFEHL
 done

 for i in {1..10}
 do
   BEFEHL
   BEFEHL
 done

if

if BEDINGUNG; then
  BEFEHL
  BEFEHL
else
  BEFEHL
  BEFEHL
fi

case

case $VARIABLE in
  a|b )
      BEFEHL
      BEFEHL
      ;;
  c )
      BEFEHL
      ;;
  * )
      BEFEHL
      ;;
 esac

Anmerkungen:

  • While- und for-Schleifen können durch "break" beendet und durch "continue" mit der nächsten Iteration fortgesetzt werden
  • BEDINGUNG ist üblicherweise ein Programm, dessen Exit-Code verwendet wird
  • Falls dazu das builtin "test" verwendet wird, ist auch die verkürzte Schreibweise: "![ TEST_PARAMETER ]" möglich (die Leerzeichen zwischen den eckigen Klammern sind wichtig!)
  • BEDINGUNG kann auch eine beliebig lange Liste von Kommandos sein - relevant ist nur der Exit-Code des letzten Befehls
  • Anstelle der Zeilenumbrüche kann natürlich auch ein Semikolon verwendet werden.

.

String-Funktionen (Variablen-Substitution)

In Abhängigkeit vom Status der Variablen

${var} die Variable
${var:-default} falls die Variable nicht gesetzt ist, wird der default-Wert zurückgegeben
${var:=default} falls die Variable nicht gesetzt ist, wird ihr der default-Wert zugewiesen
${var:+alternative} falls die Variable gesetzt ist, wird "alternative" zurückgegeben, ansonsten der Wert der Variable (also null (leer))

.

Zeichenorientiert

${#var} die Anzahl der Zeichen in "var"
${var:offset} einen Teilstring von "var", beginnend beim Zeichen Nr. "offset"
${var:offset:length} liefert "length" Zeichen von "var" beginnend bei Zeichen Nr. "offset"
${!var} gibt den Inhalt der Variablen wieder, dessen Name in var steht.

.

Ersetzungen

${var^^} wandelt alle Kleinbuchstaben in Großbuchstaben um
${var,,} wandelt alle Großbuchstaben in Kleinbuchstaben um
${var!/-/_} wandelt den ersten Bindestrich in einen Unterstrich um
${var!//-/_} wandelt alle Bindestriche in Unterstriche um
${var%regexp} entfernt das kleinstmögliche Suffix, für das "regexp" zutrifft (aus "var=gurgelFressFisch" und "regexp=F.*$" wird somit "gurgelFress"
${var%%regexp} entfernt das größtmögliche Suffix, fuer das "regexp" zutrifft (das obige Beispiel ergibt dann "gurgel")
${var#regexp} entfernt das kleinstmögliche Präfix
${var##regexp} entfernt das größtmögliche Präfix
$((10#$var)) entfernt führende Nullen

.

Gebräuchliche Beispiele

# bar=123
# foo=bar
# echo ${!foo}
123

# foo=bar.sh
# echo ${foo%.sh}
bar

Anmerkung:

  • da ein Punkt in einem regulären Ausdruck als beliebiges Zeichen interpretiert wird, würde beispielsweise auch die Endung "zsh" entfernt werden - korrekt wäre also: "${foo%..sh"

.

Berechnungen

  • mit "$((formel))" koennen einfache arithmetische Operationen ausgefuehrt werden
  • alle Variablen in der Berechnung muessen mit dem ueblichen Dollarzeichen markiert werden (fuer ash erforderlich - fuer bash nicht)
  • Variable mit führenden Nullen werden als Oktalzahl interpretiert und sollten mit vorangestellten "10#" maskiert werden, z. B. $((10#$var *3))
  • Vorsicht: i=$(($i+1)) und i=$i+1 ist fuer die bash nicht dasselbe - ersteres wird als unendliche Rekursion abgelehnt und letzteres liefert das erwartete Resultat (bei der ash funktioniert nur die erste Variante)

.

Wichtige Shell-Variablen

$1 $9:: die Parameter beim Skript-Aufruf (koennen mit "shift" weitergeschoben werden, um an die folgenden Parameter zu gelangen)
$0 der Name des Shell-Skripts
$* alle Parameter der Shell
$@ so aehnlich, wie "$*"
$# die Anzahl der Parameter
$? Exit-Code der letzten Pipeline bzw. des letzten (Vordergrund-)Kommandos
$! Exit-Code der letzten Pipeline bzw. des letzten (Hintergrund-)Kommandos
$$ die "pid" (Prozessnummer) des Skripts

.

Funktionen

Eine Funktion kann auf zweierlei Weise deklariert werden:

name () {
  BEFEHL1
  BEFEHL2
}

oder
function name {
  BEFEHL1
  BEFEHL2
}

Anmerkungen:

  • "return n" liefert den Exit-Code "n" zurück
  • Die Parameter der Funktion sind als "$1" bis "$9" verfuegbar
  • Der Aufruf einer Funktion startet einen eigenen Prozess
  • Variablen, die nur innerhalb der Funktion gültig sein sollen, müssen bei der ersten Verwendung durch ein vorangestelltestes "local" definiert werden
  • Der Aufruf einer Funktion erfolgt einfach durch ihren Namen

.

Der Befehl read

Mit read kann man vom Benutzer Eingaben als String einlesen. Der eingelesene Sting wird standardmäßig in der Variablen $REPLY gespeichert.

$ read
meine Eingabe
$ echo $REPLY
meine Eingabe

Die wichtigsten Optionen sind

-p Eingabeprompt, z.B.: -p "Press any key"
-n Nur eine bestimmte Anzahl Zeichen einlesen, z.B: -n 1
-s Keine Anzeige der Eingabe
-a Die Wörter der Eingabe in ein Array speichern, z.B. -a ,myarray

Beispiele

read -n1 -p "Press any key to continue" Nach Eingabe eines beliebigen Zeichens wird die Eingabe beendet.
read -s -p "Passwort: " PASSWD Die Eingabe wird nicht angezeigt und in der Variablen $PASSWD gespeichert

Anmelden