Aufzucht und Hege von Shell-Scripts: Wie man quotet

Wann sollte man quoten, wann nicht?

Ein guter Grundsatz für das Quoten lautet: Besser zuviel als zuwenig. Allerdings muss man ein wenig aufpassen, WIE man quotet. Prinzipiell schützt Quoting vor Fehlern durch Leerzeichen, Tabulatoren und anderen Gemeinheiten in Variablen. Es gibt allerdings auch Fälle, wo Quoting unangebracht oder einfach überflüssig ist. Ich will hier nicht alle Quoting-Regeln erklären, das findet Ihr in Tutorials, dem Bash-Manual oder in Büchern. Mir geht es um ein paar spezielle Fälle, bei denen man kreativ mit dem Quoting arbeiten kann, sowie um Regeln, wie man Quotings vor sich selbst schützt.

Es gibt Fälle, in denen ein Quoting überflüssig ist - wie bei Variablen, von denen man weiß, dass sie gesetzt und numerisch sind, z. B.:

In anderen Fällen kann es nützlich sein, wenn man das Quoting bewusst weglässt. Das folgende Beispiel nutzt das, um ein Shell-Array zu füllen:

  line="field1 field2 field3 field4"
  declare -a fld_arr
  fld_arr=($line)     # hier nicht quoten, um an einzelne Felder zu kommen
  for (( i=0; i < ${#fld_arr[*]}; i++ )); do
    echo $i ${fld_arr[$i]}
  done

Das Quoting-Zeichen ' (einfaches Hochkomma) sorgt dafür, dass im umschlossenen Text überhaupt kein Sonderzeichen mehr durch die Shell ausgewertet wird. Das wird dann zum Problem, wenn im Text eben dieses Zeichen auftaucht, es kann nämlich nicht entwertet werden, weil alle anderen verfügbaren Quoting-Zeichen wirkungslos bleiben. Ich nutze im Folgenden angepasste Versionen meiner Beiträge in einem Forum-Thread als Beispiel:

  # Hochkomma im Text
  jan@jack:~/tmp> echo '123'(456\('
  bash: syntax error near unexpected token `('
  # Versuch, das eingeschlossene Hochkomma zu entwerten
  jan@jack:~/tmp> echo '123\'(456\('
  bash: syntax error near unexpected token `('
  # So geht es: Das eingeschlossene Hochkomma wird ausserhalb der '' geparkt
  # und vor der Shell mit Anfuehrungszeichen versteckt
  jan@jack:~/tmp> echo '123'"'"'(456\('
  123'(456\(

Wie kann man diese Methode anwenden, um mit variablen, unbekannten Inhalten umgehen zu können? Als Beispiel dient ein Verzeichnis, in dem sich ein paar ganz schlimme Dateinamen herumtreiben - MP3-Dateien z. B. haben nach meiner Erfahrung oft solche wüsten Namen, manche Verwaltungsprogramme schrecken ja vor nichts zurück ;-)

  jan@jack:~/tmp/muell_namen> ls
  123'(456\(b l a)
  123'(456\(bla)
  123 "buh" 456
  jan@jack:~/tmp/muell_namen> find . -type f -printf "ls |%p|\n" |
  > sed "s/'/'\"'\"'/g;s/|/'/g" | sh
 ./123'(456\(bla)
 ./123'(456\(b l a)
 ./123 "buh" 456

Wenn man die Dateinamen einfach per ls an eine Schleife verfüttert und dann mit den Variablen weiterarbeiten will (z. B. um die Dateien umzubenennen), dann hat man mit den oben genannten Problemen zu tun. Als Ausweg dient hier das Tarnen der umschließenden Hochkommata durch ein anderes Zeichen. Die -printf-Option ist leider nur im GNU-find verfügbar. Im Beispiel wird jeder Dateiname in Pipe-Zeichen eingeschlossen (dieses Zeichen darf natürlich nicht in Dateinamen auftreten).
Anschließend geht der Dateiname durch eine sed-Wäsche. Dabei wird zuerst jedes auftretende Hochkomma durch die rettende Zeichenfolge '"'"' ersetzt, anschließend jedes Fluchtzeichen | durch ein Hochkomma - jetzt kann man mit den Dateinamen arbeiten.

Manchmal muss man auch andere Quoting-Zeichen verstecken - zum Beispiel bei geschachtelten Kommandosubstitutionen: wie kriegt man ineinander gesteckte Kommandos zum Laufen (also in einem Kommando die Ausgabe eines anderen Kommandos, das die Ausgabe eines dritten Kommandos nutzt, nutzen ;-)? Das folgende Kommando gibt den nächsten Arbeitstag in der Form "Montag, 21. Juli" aus. Dazu nutzt es ausgiebig den date-Befehl aus (funktioniert aber nur mit der GNU-Version).

  date +"%A, %d. %B" -d "today +`if test \`date +%w\` -gt 4; then \
    expr 8 - \`date +%w\`; else echo 1; fi` days"

Im Folgenden eine Step-by-Step-Erklärung der einzelnen Bestandteile des Kommandomonsters:

So, und jetzt will ich das in eine Variable schreiben. Dafür stülpe ich eine neue "`...`"-Schale um den ganzen Befehl. Was muss ich jetzt alles entwerten, damit alle Zeichen in ihrer gewünschten Bedeutung auch in der Subshell ankommen, in der sie gebraucht werden? Das ist eigentlich ganz einfach: Vor der nun hinzukommenden Shell müssen alle Zeichen verborgen werden, die für sie eine Sonderbedeutung haben. Das sind in unserem Fall: Anführungzeichen, Backslash und Backtick. Vor jedes dieser Zeichen setzen wir einfach einen Entwerter, nämlich den Backslash - fertig:

  NEXT_WDAY="`date +\"%A, %d. %B\" -d \"today +\`if test \\\`date +%w\\\` -gt 4; then \\
    expr 8 - \\\`date +%w\\\`; else echo 1; fi\` days\"`"