Bitte warten...

Bash: Strings

Im Gegensatz zu vielen anderen Programmiersprachen müssen Strings (Zeichenketten) in Bash nicht in Anführungszeichen gesetzt werden. Enthält ein String allerdings Zeichen, die auch als Metazeichen interpretiert werden können (einschließlich des Leerzeichens), kann es bei der Verarbeitung dieses Strings zu unerwarteten Ergebnissen kommen. Auf die Behandlung von Metazeichen wird weiter unten genauer eingegangen.

Zunächst betrachten wir aber nur Strings, die aus „gewöhnlichen“ Zeichen bestehen. Im weiteren Verlauf werden die verschiedenen Operationen beschrieben, die auf Strings angewendet werden können.

Bash unterstützt die Zeichenkodierung UTF-8 und damit alle Zeichen des Basic Multilingual Plane.

Code kopieren
str=Kaninchen
echo $str

Strings verketten

Strings können verkettet werden, indem einfach die Variablen ohne Zwischenraum hintereinander notiert werden. Es können auch Variablen mit Strings verkettet werden, allerdings kann das dazu führen, dass ein String als Teil eines Variablennamens interpretiert wird (Zeile 3). Um das zu umgehen, wird der Variablenname in geschweifte Klammern {} gesetzt.

Code kopieren
str1=Kaninchen
str2=züchter
echo $str1$str2verein      # Kaninchen (str2verein ist nicht bekannt)
echo ${str1}${str2}verein  # Kaninchenzüchterverein

str=Kaninchenzüchterverein
str+=sheim  # str=$str+sheim ist nicht zulässig
echo $str

Strings analysieren und zerlegen

Code kopieren
str=Kaninchenzüchterverein
echo ${#str}  # Länge von str
echo ${str:16}  # Teilstring ab Index 16 bis zum Ende des Strings
echo ${str:16:6}  # Teilstring ab Index 16 mit Länge 6 Zeichen

Auf diese Weise kann ein String zeichenweise zerlegt werden.

Code kopieren
str=Kaninchenzüchterverein
for ((i=0; i < ${#str}; i++)); do
  echo "${str:$i:1}"
done

Mit mapfile (= readarray) kann ein String zeilenweise zerlegt werden. Der Zeilenumbruch \n ist hier das Trennzeichen.

Code kopieren
mapfile -t lines <<< "$str"
for ((i=0; i < ${#lines[@]}; i++)); do
  echo "Zeile $i: ${lines[i]}"
done

Mit mapfile kann ein String auch an einem beliebigen anderen Trennzeichen aufgetrennt werden. Dieses Trennzeichen wird nach der Option -d (delimiter) angegeben.

Code kopieren
mapfile -d ";" -t lines <<< "$str"
for ((i=0; i < ${#lines[@]}; i++)); do
  echo "Teilstring $i: ${lines[i]}"
done

Bash kennt keine Analyse einzelner Zeichen wie die Funktion ord() in anderen Programmier­sprachen, bei der der Unicode-Codepoint eines Zeichens ermittelt wird. Diese Funktion muss nachgebildet werden (hier bis max. 4 Bytes pro Zeichen).

Code kopieren
codepoint() {  # returns decimal codepoint of unicode character
  read -ra hex <<< $(echo -n "${1}" | od -t x4)
  hex=(${hex[1]:6:2} ${hex[1]:4:2} ${hex[1]:2:2} ${hex[1]:0:2})
  bin=()
  for e in "${hex[@]}"; do
    bin+=($(printf %08d $(echo "obase=2;ibase=16;${e^^}" | bc)))
  done
  if [[ ${bin[0]:0:1} == 0 ]]; then  # 1 byte character
    bin=${bin[0]}
  elif [[ ${bin[0]:0:3} == 110 ]]; then  # 2 byte character
    bin=${bin[0]:3:5}${bin[1]:2:6}
  elif [[ ${bin[0]:0:4} == 1110 ]]; then  # 3 byte character
    bin=${bin[0]:4:4}${bin[1]:2:6}${bin[2]:2:6}
  elif [[ ${bin[0]:0:5} == 11110 ]]; then  # 4 byte character
    bin=${bin[0]:5:3}${bin[1]:2:6}${bin[2]:2:6}${bin[3]:2:6}
  else  # unknown character
    bin=-1; echo -1
  fi
  if [[ $bin != -1 ]]; then
    echo $(echo "obase=10;ibase=2;${bin}" | bc)
  fi
}

str="Katzen züchter verein ffi"
for ((i=0; i<${#str}; i++)); do
  char=${str:$i:1}
  echo "${char} "$(codepoint "$char")
done

Ein Unicode-Zeichen eines bestimmten Codepoints im Bereich von U+0000 bis U+FFFF (max. 3 Bytes pro Zeichen) lässt sich dagegen folgendermaßen ausgeben:

Code kopieren
c=(75 97 116 122 101 110 32 122 252 99 104 116 101 114 32 118 101 114 101 105 110 32 64259)
for ((i=0; i<${#c[@]}; i++)); do
  echo -ne "\u$(echo "obase=16;ibase=10;${c[i]}" | bc)"
done
echo

Vorkommen eines Teilstrings prüfen

Ob ein Teilstring in einem String enthalten ist, kann man auf folgende Weise prüfen. Die Bedingung in den doppelten eckigen Klammern vergleicht den den String str mit einem String, der aus beliebigen Zeichen (oder keinen) gefolgt von dem String search gefolgt von weiteren beliebigen Zeichen (oder keinen) besteht. Die beliebigen Zeichen werden durch das Steuerzeichen * repräsentiert.

Code kopieren
str=Kaninchenzüchterverein
search=züchter
if [[ $str == *$search* ]]; then echo "ist enthalten"
else echo "ist nicht enthalten"
fi  # ist enthalten

search=sammler
if [[ $str == *$search* ]]; then echo "ist enthalten"
else echo "ist nicht enthalten"
fi  # ist nicht enthalten

Teilstring ersetzen

Einen Teilstring zu ersetzen ist sehr einfach. Im folgenden Beispiel wird der Teilstring verein innerhalb von str durch den String club ersetzt. Es wird nur das erste Vorkommen des Teilstrings ersetzt, falls dieser mehrfach vorhanden ist. Wird kein String angegeben, durch den der Teilstring ersetzt werden soll, so wird das erste Vorkommen des Teilstrings gelöscht (leere Ersetzung).

Sollen alle Vorkommen eines Teilstrings ersetzt oder gelöscht werden, so wird der erste Slash in dem Ausdruck verdoppelt.

Code kopieren
str=Kaninchenzüchterverein
echo ${str/verein/club}  # ersetzt das erste Vorkommen von verein durch club
echo ${str/verein}  # löscht das erste Vorkommen von verein

str=${str//n/x}  # ersetzt alle Vorkommen von n durch x
echo $str
str=${str//x}  # löscht alle Vorkommen von x
echo $str

Teilstring von Anfang oder Ende entfernen

Mit den Operatoren #, % und ? kann ein Teilstring vom Anfang bzw. vom Ende eines Strings entfernt werden.

  • # entfernt bis zum ersten Vorkommen von search ab Anfang
  • ## entfernt bis zum letzten Vorkommen von searchab Anfang
  • % entfernt ab letztem Vorkommen von searchbis zum Ende
  • %% entfernt ab ersten Vorkommen von searchbis zum Ende

Man beachte die Position des *-Zeichens. Das ?-Zeichen gibt die Anzahl der Zeichen an, die vom Anfang bzw. Ende des Strings entfernt werden sollen.

Code kopieren
str=Kaninchenzüchterverein
search="ch"
echo ${str#*$search}   # enzüchterverein
echo ${str##*$search}  # terverein
echo ${str%ch*}        # Kaninchenzü
echo ${str%%ch*}       # Kanin

echo ${str#????}       # nchenzüchterverein
echo ${str%????}       # Kaninchenzüchterve

Position eines Teilstrings ermitteln

Um die Position eines Teilstrings zu bestimmen, hat Bash keine eigene Funktion, sie lässt sich aber folgendermaßen nachbilden. Zunächst wird in der Variable rest ein String gespeichert, der den String str bis zur Position der Übereinstimmung mit search gefolgt von beliebigen Zeichen (oder keinen) minus 1 enthält. Die Länge von rest entspricht daher der Position von search innerhalb von str.

Falls search in str nicht enthalten ist, entspricht die Länge von rest der von str. In diesem Fall wird -1 ausgegeben.

Code kopieren
str=Kaninchenzüchterverein
search=züchter
rest=${str%%$search*}
if [[ ${#rest} != ${#str} ]]; then echo ${#rest}; else echo -1; fi

Groß- und Kleinschreibung

Um die Groß- oder Kleinschreibung einzelner Zeichen eines Strings festzulegen, werden die Operatoren ^ und , verwendet.

Code kopieren
str=Kaninchenzüchterverein
echo ${str^}       # wandelt das erste Zeichen des Strings in einen Großbuchstaben
echo ${str,}       # wandelt das erste Zeichen des Strings in einen Kleinbuchstaben
echo ${str^^}      # wandelt alle Zeichen des Strings in Großbuchstaben
echo ${str,,}      # wandelt alle Zeichen des Strings in Kleinbuchstaben
echo ${str^^[ch]}  # wandelt nur die Zeichen c und h in Großbuchstaben
echo ${str,,[KN]}  # wandelt nur die Zeichen K und N in Kleinbuchstaben

Farben

Mit tput ist es möglich, Text in der Terminalausgabe auf verschiedenste Art hervorzuheben. Zum Beispiel kann die Schriftfarbe mit $(tput setaf X) und die Hintergrundfarbe mit $(tput setab X) eingestellt werden, wobei X eine Zahl zwischen 0 und 255 sein kann. Wird statt einer Zahl das Schlüsselwort init verwendet, wird die Farbe zurückgesetzt.

Die ersten 16 Farben sind hier dargestellt:

 0 schwarz 8 dunkelgrau
 1 rot 9 hellrot
 2 grün 10 hellgrün
 3 gelb 11 hellgelb
 4 blau 12 hellblau
 5 violett 13 hellviolett
 6 türkis 14 helltürkis
 7 grau 15 hellgrau

Folgender Code erzeugt eine Tabelle mit weiteren 63 = 216 Farben und 24 shades of grey:

Code kopieren
clear
for r in {0..5}; do
  for g in {0..5}; do
    for b in {0..5}; do
      f=$((16+36*r+6*g+b))
      echo -n "$(tput setab $f)   "
    done
    echo "$(tput setab init)"
  done
done

for row in {0..3}; do
  for col in {0..5}; do
    f=$((232+6*row+col))
    echo -n "$(tput setab $f)   "
  done
  echo "$(tput setab init)"
done

24-Bit-Farben in hexadezimaler Notation können mit folgender Funktion an die von tput unterstützen Farben angenähert werden.

Code kopieren
export LC_NUMERIC=en_US.UTF-8  # printf: Punkt statt Komma als Dezimaltrenner
hex2tput() {
  local rgb=${1^^}  # alle Buchstaben in Großbuchstaben
  local dec=16  # überspring die ersten 16 Farben
  for i in {0..2}; do
    local d=$(echo "obase=10;ibase=16;${rgb:$((i*2)):2}" | bc)  # konvertiert jeden Farbkanal von hexadezimal nach dezimal
    d=$(echo "$d/51" | bc)  # konvertiert 256 Stufen nach 6 Stufen pro Kanal
    d=$(printf %.0f $d)  # rundet auf Ganzzahl
    dec=$(echo "$dec+6^(2-$i)*$d" | bc)  # addiert alle Kanäle
  done
  echo "$dec"
}

clear
colors=(c71585 ff8c00 ffd700 adff2f 32cd32 4682b4 1e90ff 8a2be2)
for c in "${colors[@]}"; do
  color="$(hex2tput $c)"
  echo -n "$(tput setab $color)   $(tput sgr0) "
done
echo

Es können aber auch alle 24-Bit-Farben verwendet werden, allerdings mit einer etwas kryptischeren Syntax (s. ANSI-Escapesequenz). Der obere Block ändert die Schriftfarbe, der untere die Hintergrundfarbe. Es müssen Werte zwischen 0 und 255 für jeden der drei Farbkanäle Rot, Grün und Blau angegeben werden.

Der Vorteil dieser Notation ist außerdem, dass die Ausführung im Gegensatz zu tput etwa sechzigmal schneller ist.

Code kopieren
echo -e '\e[38;2;255;0;0;2mHallo, Welt!\e[m'
echo -e '\e[38;2;0;255;0;2mHallo, Welt!\e[m'
echo -e '\e[38;2;0;0;255;2mHallo, Welt!\e[m'

echo -e '\e[48;2;255;0;0;2mHallo, Welt!\e[m'
echo -e '\e[48;2;0;255;0;2mHallo, Welt!\e[m'
echo -e '\e[48;2;0;0;255;2mHallo, Welt!\e[m'

# \e[38;2; bzw. \e[48;2; leitet die Änderung ein
# \e[m beendet sie wieder

for r in {0..255}; do
  echo -en '\e[48;2;'${r}';0;0;2m \e[m'
done
echo

Weitere Hervorhebungen

Weitere mögliche Hervorhebungen sind hier dargestellt:

Code kopieren
echo "Foo $(tput bold)Hallo, Welt!$(tput bold init) Bar"  # fett
echo "Foo $(tput sitm)Hallo, Welt!$(tput ritm) Bar"  # kursiv
echo "Foo $(tput smul)Hallo, Welt!$(tput rmul) Bar"  # unterstrichen
echo "Foo $(tput rev)Hallo, Welt!$(tput rev init) Bar"  # invertiert
echo "Foo $(tput dim)Hallo, Welt!$(tput dim init) Bar"  # abgedunkelt
echo "Foo $(tput invis)Hallo, Welt!$(tput invis init) Bar"  # unsichtbar
echo "Foo $(tput blink)Hallo, Welt!$(tput blink init) Bar"  # blinkend

echo -e "Foo \e[21mHallo, Welt!\e[24m Bar"  # doppelt unterstrichen
echo -e "Foo \e[4:3mHallo, Welt!\e[4:0m Bar"  # gewellt unterstrichen
echo -e "Foo \e[9mHallo, Welt!\e[29m Bar"  # durchgestrichen
echo -e "Foo \e[53mHallo, Welt!\e[55m Bar"  # überstrichen

Hervorhebungen können auch geschachtelt werden. Mit $(tput sgr0) werden alle Hervorhebungen zurückgesetzt.

Hyperlinks

Auf folgende Weise ist es möglich, Hyperlinks in der Ausgabe darzustellen, sofern der Terminaltreiber diese Funktion unterstützt. Im Gnome-Terminal kann ein Hyperlink mit Rechtsklick ► Verweis öffnen im Browser geöffnet werden.

Code kopieren
echo -e '\e]8;;https://www.decocode.de\e\\Hyperlink\e]8;;\e\\'

Auflösung von Escape-Sequenzen

Mit der Option -e des Kommandos echo können Escape-Sequenzen aufgelöst werden.

Im folgenden Beispiel wird das für die Escape-Sequenzen \t (Tabulatorschritt), \n (Zeilenumbruch) und \u (Unicode-Zeichen) demonstriert:

Code kopieren
echo -e "Es folgt ein Tabulatorschritt\tund ein Umbruch\nin die nächste Zeile."
echo -e "\u1f00\u03c4\u03b1\u03C1\u03B1\u03BE\u03AF\u03B1"  # Hexadezimalzahlen können große und kleine Buchstaben enthalten.

Maskierung von Metazeichen

Die fehlende Maskierung von Metazeichen kann zu Laufzeitfehlern, Sicherheitslücken oder gar Datenverlust führen, weshalb es wichtig ist, bei der Verwendung von Strings diesem Aspekt große Aufmerksamkeit zu schenken und die Maskierungen im eigenen Quelltext mit verschiedenen Metazeichen zu testen!

Metazeichen sind Zeichen, die im Gegensatz zu gewöhnlichen Schriftzeichen (Literale) eine besondere Funktion erfüllen. Sollen solche Zeichen von der Bash als gewöhnliche Schriftzeichen interpretiert werden, müssen sie durch Escaping oder Quoting maskiert werden.

Metazeichen sind beispielsweise:

  •   – Leerzeichen trennen die Argumente von Kommandos.
  • " – Doppelte Anführungszeichen kennzeichnen Teilstrings, in denen die Funktion bestimmter Metazeichen aufgehoben ist.
  • ' – Einfache Anführungszeichen kennzeichnen Teilstrings, in denen die Funktion bestimmter Metazeichen aufgehoben ist.
  • \ – Backslash hebt die Funktion von Metazeichen auf.
  • - – Minuszeichen leitet Kommando-Optionen ein.
  • * – Asterisk ist ein Platzhalter für beliebige Zeichen (Wildcard).
  • @At-Zeichen ist ein Platzhalter für beliebige Zeichen (Wildcard).
  • $ – Dollarzeichen expandiert Variablen oder andere Konstrukte.
  • ? – Fragezeichen enthält den Return-Code des letzten Kommandos.
  • |Pipe übergibt den Rückgabewert eines Kommandos an ein anderes Kommando zur Verarbeitung.
  • < – Kleiner-als-Zeichen liest aus einer Datei.
  • > – Größer-als-Zeichen schreibt in eine Datei.
  • #Hash-Zeichen leitet einen Kommentar ein.
  • : – Doppelpunkt repräsentiert ein „leeres“ Kommando.
  • ; – Semikolon beendet eine Anweisungszeile.

Escaping mit Backslash

Der Backslash \ kann die Funktion von Metazeichen aufheben.

Code kopieren
# Escaping von Leerzeichen bei Strings als Wert einer Variablen
str=Hallo,\ Welt!
echo $str

# Escaping von Dollarzeichen. Die Variable str wird nicht expandiert
echo \$str

# Escaping von doppelten Anführungszeichen
echo "Sag: \"Hallo, Welt!\""

# Escaping von Asterisk, der hier sonst Dateiobjekte auflisten würde
echo \*

# Escaping von Backslash
echo \\o/

# Escaping von Größer-als-Zeichen
if [ "Foo" \> "Bar" ]; then echo "Foo > Bar"; fi

# Escaping von Kleiner-als-Zeichen
if [ "Foo" \< "Bar" ]; then echo "Foo < Bar"; fi

# Escaping des Hash-Zeichens
echo \# Hallo, Welt!

Quoting mit doppelten Anführungszeichen

Doppelte Anführungszeichen "" ersparen einem das Escapen mit dem Backslash, da viele der oben erwähnten Metazeichen zwischen doppelten Anführungszeichen automatisch maskiert werden, so beispielsweise das Leerzeichen, *, <, >, '.

Das Dollarzeichen $ und die geschwungenen Klammern {} werden allerdings nicht maskiert, sodass die Expansion von Variablen innerhalb von doppelten Anführungszeichen weiterhin möglich ist.

Soll das doppelte Anführungszeichen selbst dargestellt werden, wird es mit dem Backslash escapet.

Code kopieren
str=Osterhase
echo $str  # Osterhase
str=Hallo,\ Welt!
echo $str  # Hallo, Welt!
str=Hallo," "Welt!
echo $str  # Hallo, Welt!
str=Hallo, Welt!
echo $str  # erzeugt einen Fehler

a=Welt
echo "Hallo, ${a}!" v # Hallo, Welt!
echo "Hallo, \${a}!"  # Hallo, ${a}!

echo "Sie rief: \"Hallo, Welt!\""

Beim Zeichen *, das die Funktion einer Wildcard besitzt, genügt es allerdings nicht, es zu escapen oder in Anführungszeichen zu setzen. Hier muss selbst die Variable bei der Ausgabe in Anführungszeichen gesetzt werden (Zeilen 3 und 6).

Sonst bewirkt echo * lediglich die Ausgabe aller Dateien und Verzeichnisse im aktiuellen Arbeitsverzeichnis.

Code kopieren
str=*
echo $str   # Liste der Objekte im Arbeitsverzeichnis
echo "$str"  # *
str="*"
echo $str   # Liste der Objekte im Arbeitsverzeichnis
echo "$str"  # *
str=\*
echo $str   # Liste der Objekte im Arbeitsverzeichnis
str="\*"
echo $str    # \*
echo "$str"  # \*
echo "*"     # *

Quoting mit einfachen Anführungszeichen

Zwischen einfachen Anführungszeichen ' werden noch weitere Metazeichen maskiert, so beispielsweise der Backslash \, das Dollarzeichen $ und die geschwungenen Klammern {}. Damit ist die Expansion von Variablen innerhalb von einfachen Anführungszeichen nicht mehr möglich.

Das einfache Anführungszeichen kann innerhalb einfacher Anführungszeichen nicht mit Backslash escapet werden, außerhalb aber schon.

Code kopieren
a=Welt
echo "Hallo, ${a}!"  # Hallo, Welt!
echo 'Hallo, ${a}!'  # Hallo, ${a}!

echo 'Dies ist ein Backslash \.'     # Dies ist ein Backslash \.


echo 'Sie rief: \'Hallo, Welt!\''    # führt zu einem Fehler
echo 'Sie rief: '\''Hallo, Welt!'\'  # Sie rief: 'Hallo, Welt!'