Tracks
/
Bash
Bash
/
Lehrplan
/
Warum Quoting wichtig ist
Wa

Warum Quoting wichtig ist in Bash

{one: "1 Übung", other: "%{count} Übungen"}

Über Warum Quoting wichtig ist

Einen Ratschlag werden neue Bash-Programmierer immer wieder hören:

Setz deine Variablen in Anführungszeichen!

Warum ist das so?

Wenn du eine Variable expandierst (wie $name), möchtest du fast immer den Wert der Variable als einen einzelnen String (oder eine Zahl) verwenden. Du willst nicht, dass der Wert unerwartet als zwei oder mehr getrennte Strings behandelt wird.

Quoting

Quoting ist der wichtigste Mechanismus, um sicherzustellen, dass ein Stück Text als ein einzelnes Wort behandelt wird. Es gibt zwei wichtige Arten von Anführungszeichen.

Doppelte Anführungszeichen

Doppelte Anführungszeichen erlauben innerhalb von ihnen Parameterexpansion, arithmetische Expansion und Kommandosubstitution.

echo "This is bash version $BASH_VERSION"

Du kannst ein wörtliches doppeltes Anführungszeichen in einen String in doppelten Anführungszeichen einfügen, indem du einen Backslash davor setzt.

echo "She said \"Let me teach you about bash.\""

Einfache Anführungszeichen

Einfache Anführungszeichen erlauben keine Erweiterungen in ihrem Inneren.

echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION

Es ist nicht möglich, ein einfaches Anführungszeichen in einen String in einfachen Anführungszeichen einzubetten.

Warum Quoting wichtig ist

Was genau ist an echo $name falsch?

Wenn die Shell eine Codezeile parst, führt sie mehrere Arten von Erweiterungen der Reihe nach aus. Zwei der letzten Erweiterungen sind die Wortaufteilung und die Dateinamenerweiterung.

Wortaufteilung

Alle Ergebnisse von Parameterexpansion, Kommandosubstitution oder arithmetischer Expansion, die nicht innerhalb von doppelten Anführungszeichen entstanden sind, kommen für die „Wortaufteilung" infrage. Die Shell nimmt das expandierte Ergebnis und teilt es an jeder Folge von Zeichen auf, die im Wert von $IFS vorkommt. Standardmäßig sind das Leerzeichen, Tabulator und Zeilenumbruch.

Wir können das mit einer Shell-Schleife in Aktion sehen:

sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^

gibt aus

1 The quick brown fox jumps.

Aber ohne Anführungszeichen:

i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^

gibt aus

1 The
2 quick
3 brown
4 fox
5 jumps.

Schauen wir es uns mit einem anderen Wert von $IFS an:

csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done

gibt aus

first
second
third
Wortaufteilung deaktivieren

Eine Technik, um die Wortaufteilung zu deaktivieren, ist, $IFS auf einen leeren String zu setzen

sentence="The quick brown fox jumps."
IFS=    # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^

gibt aus

1 The quick brown fox jumps.

Es ist jedoch nicht empfehlenswert, Variablen ohne Anführungszeichen zu lassen, es sei denn, du deaktivierst auch die Dateinamenerweiterung. Wie das geht, sehen wir weiter unten.

Dateinamenerweiterung

„Glob"-Muster werden verwendet, um ein knappes Muster auszudrücken, das auf eine Menge von Dateien passt. Zum Beispiel kannst du alle Markdown-Dateien in einem Verzeichnis mit *.md treffen. Glob-Muster gibt es seit der Erfindung von Unix.

Die Glob-Platzhalterzeichen sind:

  • * trifft auf null oder mehr beliebige Zeichen zu,

  • ? trifft auf genau ein beliebiges Zeichen zu,

  • [...] trifft auf genau ein Zeichen aus der Menge der in den eckigen Klammern aufgeführten Zeichen zu.

    Advanced
    Es gibt viele Details zu Zeichensätzen. Klicke hier, um sie anzuzeigen.
    • [abc] trifft auf genau ein Zeichen zu, das entweder ein a oder ein b oder ein c ist.
      • um eine wörtliche schließende Klammer in den Ausdruck aufzunehmen, mache sie zum ersten Zeichen nach der öffnenden Klammer (nach einem optionalen ^)
        • [][] ist ein Zeichensatz, der entweder auf eine schließende oder eine öffnende Klammer passt.
        • oder maskiere sie mit einem Backslash: [[\]]
    • [x-y] trifft auf genau ein Zeichen zu, das im Bereich von x bis y liegt
      • um einen wörtlichen Bindestrich in einen Zeichensatz aufzunehmen, muss er entweder das erste Zeichen nach der öffnenden Klammer (nach einem optionalen ^) oder das letzte Zeichen vor der schließenden Klammer sein.
        • [-xy] oder [xy-] treffen jeweils auf genau ein Zeichen zu, das ein - oder ein x oder ein y ist
        • oder maskiere ihn mit einem Backslash: [x\-y]
      • es können mehrere Bereiche angegeben werden, zum Beispiel trifft [0-9A-Fa-f] auf eine hexadezimale Ziffer zu
    • [^abc] trifft auf genau ein Zeichen zu, das NICHT ein a oder ein b oder ein c ist,
    • [^x-y] trifft auf genau ein Zeichen zu, das NICHT im Bereich von x bis y liegt
    • [[:character_class:]] trifft auf ein Zeichen zu, das in der benannten "character_class" liegt
      • die verfügbaren Zeichenklassen sind:
        • lower -- Kleinbuchstaben
        • upper -- Großbuchstaben
        • alpha -- Buchstaben
        • digit -- Ziffern
        • alnum -- Buchstaben und Ziffern
        • space -- Leerraum
        • blank -- horizontaler Leerraum (Leerzeichen und Tabulator)
        • xdigit -- hexadezimale Ziffern
        • word -- Zeichen, die in einem Bezeichner erlaubt sind (Buchstaben, Zahlen, Unterstrich)
        • punct -- Interpunktion
        • cntrl -- Steuerzeichen
        • graph -- "sichtbare" Zeichen
        • print -- sichtbare Zeichen plus Leerzeichen
      • sie können kombiniert werden:
        • die Klasse [[:alpha:]] besteht aus [[:lower:][:upper:]]
        • die Klasse [[:alnum:]] besteht aus [[:alpha:][:digit:]]
        • die Klasse [[:word:]] besteht aus [[:alnum:]_]
        • der Unterstrich (Oktal 137) ist sowohl [[:word:]] als auch [[:punct:]]
        • die Klasse [[:graph:]] besteht aus [[:alnum:][:punct:]]
        • die Klasse [[:print:]] besteht aus [[:graph:] ] -- nur Leerzeichen (Oktal 040), kein anderer Leerraum.
Über eine Liste von Dateien iterieren

Du kannst mit einer for-Schleife über eine Liste von Dateien iterieren

for file in *.csv; do
    do_something_with "$file"
done

Beachte, dass die Variable "$file" in Anführungszeichen steht. Das Glob-Muster kann Dateien zurückgeben, deren Name Leerzeichen enthält. Wir müssen die Variable in Anführungszeichen setzen, damit der Befehl do_something_with genau ein Argument erhält, nämlich den Dateinamen.

Glob-Muster sind keine regulären Ausdrücke

Glob-Muster sind keine regulären Ausdrücke.

  • auf genau ein Zeichen passen
    • Regex .
    • Glob ?
  • auf null oder mehr Zeichen passen
    • Regex .*
    • Glob *
      • das Glob-Muster .* bedeutet „auf einen Dateinamen passen, bei dem das erste Zeichen ein wörtlicher Punkt ist, gefolgt von null oder mehr beliebigen Zeichen".
  • auf ein oder mehr Zeichen passen
    • Regex .+
    • Glob ?*
  • Regex-Zeichensätze und Glob-Zeichensätze verhalten sich gleich.
Advanced

Bash bietet erweiterte Muster, die näher an regulären Ausdrücken sind.

Quoting und Dateinamenerweiterung

Es gibt Situationen, in denen du die Dateinamenerweiterung unterdrücken möchtest. Der Umgang mit Benutzereingaben ist eine davon. Betrachte dieses Code-Schnipsel

read -p "Enter something: " user_input
echo $user_input

Wenn der Benutzer ein Sternchen (*) eingibt, was wird dann ausgegeben? Die Liste der Dateien im aktuellen Verzeichnis. Das ist wahrscheinlich nicht, was du willst.

Ein weiteres Beispiel ist, Muster als Argumente an Programme zu übergeben, die Muster erwarten. Das Programm tr wird oft zur Textmanipulation verwendet. Um den Wert einer Variable in Kleinbuchstaben umzuwandeln, kannst du das hier machen

var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'

gibt aus

my puppet

Ein häufiger Fehler ist, die Argumente von tr nicht in Anführungszeichen zu setzen: tr [:upper:] [:lower:]

Caution

Warum ist das ein Problem? Nicht in Anführungszeichen gesetzte Glob-Muster werden zur Liste der passenden Dateien expandiert. Angenommen, die Person, die dein Skript verwendet, hat Dateien mit den Namen p und u und w im aktuellen Verzeichnis:

touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]

Das führt zu einem Fehler!

Das nicht in Anführungszeichen gesetzte Muster [:upper:] passt auf jedes von :, u, p, e oder r. Das nicht in Anführungszeichen gesetzte Muster [:lower:] passt auf jedes von :, l, o, w, e oder r. Bash findet für das erste Muster die Dateien p und u und für das zweite Muster die Datei w, und tr wird so aufgerufen

echo "$var" | tr p u w

Und das ist die falsche Anzahl von Argumenten für tr.

Dateinamenerweiterung deaktivieren

Du kannst den Befehl set verwenden, um die Dateinamenerweiterung zu deaktivieren.

echo *      # prints a list of files
set -f      # disable filename expansion
echo *      # prints a literal asterisk
set +f      # enable filename expansion
Dateinamenerweiterung steuern

Es gibt mehrere Einstellungen für den eingebauten Befehl shopt, die steuern, wie die Dateinamenerweiterung arbeitet. Ein paar interessante sind:

  • shopt -s nocaseglob -- Groß- und Kleinschreibung beim Abgleich ignorieren
  • shopt -s extglob -- erweiterte Muster aktivieren
  • shopt -s nullglob -- wenn keine Dateien passen, wird das Muster durch nichts ersetzt. Das Standardverhalten ist, das Muster als wörtlichen String stehen zu lassen.
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab