Wann ist eine doppelte Anführungszeichen erforderlich?

120

Der alte Rat bestand früher darin, jeden Ausdruck mit einem doppelten Anführungszeichen zu versehen $VARIABLE, zumindest wenn man wollte, dass er von der Shell als ein einzelnes Element interpretiert wird, da sonst Leerzeichen im Inhalt von $VARIABLEdie Shell abwerfen würden.

Ich verstehe jedoch, dass in neueren Versionen von Shells nicht mehr immer doppelte Anführungszeichen erforderlich sind (zumindest für den oben beschriebenen Zweck). Zum Beispiel in bash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

In zsh, auf der anderen Seite die gleichen drei Befehle folgen. Basierend auf diesem Experiment scheint es daher, dass man in diesen Fällen bashdie doppelten Anführungszeichen weglassen kann [[ ... ]], aber weder innerhalb [ ... ]noch in Befehlszeilenargumenten, während zshin diesen Fällen die doppelten Anführungszeichen weggelassen werden können.

Aber aus Anekdotenbeispielen wie den obigen allgemeine Regeln abzuleiten, ist ein Zufallsprinzip. Es wäre schön, eine Zusammenfassung darüber zu sehen, wann doppelte Anführungszeichen erforderlich sind. Ich bin in erster Linie interessiert an zsh, bashund /bin/sh.

kjo
quelle
10
Ihr beobachtetes Verhalten in zsh hängt von den Einstellungen ab und wird von der SH_WORD_SPLITOption beeinflusst.
Ulrich Dangel
3
Nebenbei bemerkt: All-Caps-Variablennamen werden von Variablen verwendet, die für das Betriebssystem und die Shell von Bedeutung sind. In der POSIX-Spezifikation wird ausdrücklich empfohlen, für anwendungsdefinierte Variablen Namen in Kleinbuchstaben zu verwenden. (Während sich die angegebene Spezifikation speziell auf Umgebungsvariablen konzentriert, teilen sich Umgebungsvariablen und Shell-Variablen einen Namespace: Der Versuch, eine Shell-Variable mit einem Namen zu erstellen, der bereits von einer Umgebungsvariablen verwendet wird, überschreibt letztere.) Siehe pubs.opengroup.org/onlinepubs/009695399/basedefs/… , vierter Absatz.
Charles Duffy

Antworten:

128

Zuerst trenne zsh vom Rest. Es geht nicht um alte oder moderne Shells: zsh verhält sich anders. Die zsh-Designer haben beschlossen, es mit herkömmlichen Shells (Bourne, ksh, bash) nicht kompatibel zu machen, aber einfacher zu verwenden.

Zweitens ist es viel einfacher, immer doppelte Anführungszeichen zu verwenden, als sich zu merken, wann sie benötigt werden. Sie werden die meiste Zeit benötigt, daher müssen Sie lernen, wann sie nicht benötigt werden und wann sie nicht benötigt werden.

Kurz gesagt, sind doppelte Anführungszeichen immer dann erforderlich, wenn eine Liste von Wörtern oder Mustern erwartet wird . Sie sind in Kontexten optional, in denen der Parser eine Rohzeichenfolge erwartet.

Was passiert ohne Anführungszeichen?

Beachten Sie, dass ohne doppelte Anführungszeichen zwei Dinge passieren.

  1. Zunächst wird das Ergebnis der Erweiterung (der Wert der Variablen für eine Parametersubstitution wie ${foo}oder die Ausgabe des Befehls für eine Befehlssubstitution wie $(foo)) in Wörter aufgeteilt, wo immer Leerzeichen enthalten sind.
    Genauer gesagt wird das Ergebnis der Erweiterung auf jedes Zeichen aufgeteilt, das im Wert der IFSVariablen erscheint (Trennzeichen). Wenn eine Folge von Trennzeichen Leerzeichen (Leerzeichen, Tabulatoren oder Zeilenumbrüche) enthält, wird das Leerzeichen als einzelnes Zeichen gezählt. Führende, nachfolgende oder wiederholte Nicht-Leerzeichen-Trennzeichen führen zu leeren Feldern. Zum Beispiel mit IFS=" :", :one::two : three: :four produziert leere Felder vor one, zwischen oneund twound (einem einzigen) , die zwischen threeund four.
  2. Jedes Feld, das aus der Aufteilung resultiert, wird als Glob (ein Platzhaltermuster) interpretiert, wenn es eines der Zeichen enthält \[*?. Wenn dieses Muster mit einem oder mehreren Dateinamen übereinstimmt, wird das Muster durch die Liste der übereinstimmenden Dateinamen ersetzt.

Eine nicht in Anführungszeichen gesetzte Variablenerweiterung $foowird umgangssprachlich als "split + glob-Operator" bezeichnet, im Gegensatz dazu wird "$foo"nur der Wert der Variablen verwendet foo. Gleiches gilt für die Befehlsersetzung: "$(foo)"Ist eine Befehlsersetzung, $(foo)ist eine Befehlsersetzung, gefolgt von split + glob.

Wo Sie die doppelten Anführungszeichen weglassen können

Hier sind alle Fälle, die mir in einer Bourne-artigen Shell einfallen, in der Sie eine Variable oder eine Befehlsersetzung ohne doppelte Anführungszeichen schreiben können und der Wert wörtlich interpretiert wird.

  • Auf der rechten Seite einer Aufgabe.

    var=$stuff
    a_single_star=*
    

    Beachten Sie, dass Sie die doppelten Anführungszeichen nach benötigen export, da es sich um ein gewöhnliches integriertes Schlüsselwort handelt, nicht um ein Schlüsselwort. Dies gilt nur für einige Shells wie dash, zsh (in sh emulation), yash oder posh. Bash und Ksh behandeln beide exportspeziell.

    export VAR="$stuff"
  • In einer caseErklärung.

    case $var in 

    Beachten Sie, dass Sie in einem Fallmuster doppelte Anführungszeichen benötigen. Die Wortteilung findet in einem Fallmuster nicht statt, aber eine nicht in Anführungszeichen gesetzte Variable wird als Muster interpretiert, während eine in Anführungszeichen gesetzte Variable als Literalzeichenfolge interpretiert wird.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
    
  • In doppelten Klammern. Doppelte Klammern sind Shell-Spezialsyntax.

    [[ -e $filename ]]

    Außer dass Sie doppelte Anführungszeichen benötigen, wenn ein Muster oder ein regulärer Ausdruck erwartet wird: auf der rechten Seite von =oder ==oder !=oder =~.

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi
    

    Sie benötigen wie gewohnt doppelte Anführungszeichen in einfachen Klammern, [ … ]da es sich um eine gewöhnliche Shell-Syntax handelt (es handelt sich um einen Befehl, der zufällig aufgerufen wird [). Siehe Einzel- oder Doppelklammern

  • In einer Umleitung in nicht interaktiven POSIX-Shells (weder bashnoch ksh88).

    echo "hello world" >$filename

    Bei einigen interaktiven Shells wird der Wert der Variablen als Platzhaltermuster behandelt. POSIX verbietet dieses Verhalten in nicht interaktiven Shells, aber einige Shells, einschließlich bash (außer im POSIX-Modus) und ksh88 (auch wenn sie als (angeblich) POSIX sheiniger kommerzieller Unices wie Solaris gefunden wurden), tun es dort noch ( bashversuchen auch, sie zu teilen) und die Umleitung schlägt fehl, es sei denn, das Aufteilen + Verschieben ergibt genau ein Wort. Aus diesem Grund ist es besser, Umleitungsziele in einem shSkript anzugeben, falls Sie es eines bashTages in ein Skript konvertieren oder es auf einem System ausführen möchten, auf dem es sich shbefindet nicht konform zu diesem Punkt, oder es kann sein sourced von interaktiven Shells.

  • Innerhalb eines arithmetischen Ausdrucks. Tatsächlich müssen Sie die Anführungszeichen weglassen, damit eine Variable als arithmetischer Ausdruck analysiert wird.

    expr=2*2
    echo "$(($expr))"
    

    Sie benötigen jedoch die Anführungszeichen um die arithmetische Erweiterung, da sie in den meisten Shells der Wortteilung unterliegen, wie es POSIX erfordert (!?).

  • In einem assoziativen Array-Index.

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"
    

Eine nicht in Anführungszeichen gesetzte Variable und eine Befehlsersetzung können in einigen seltenen Fällen hilfreich sein:

  • Wenn der Variablenwert oder die Befehlsausgabe aus einer Liste von Glob-Mustern besteht und Sie diese Muster auf die Liste der übereinstimmenden Dateien erweitern möchten.
  • Wenn Sie wissen, dass der Wert kein Platzhalterzeichen enthält, $IFSwurde das nicht geändert, und Sie möchten es in Leerzeichen aufteilen.
  • Wenn Sie einen Wert auf ein bestimmtes Zeichen aufteilen möchten: Deaktivieren Sie das Globbing mit set -f, stellen Sie IFSdas Trennzeichen ein (oder lassen Sie es in Leerzeichen aufteilen), und führen Sie dann die Erweiterung durch.

Zsh

In zsh können Sie mit wenigen Ausnahmen die doppelten Anführungszeichen meistens weglassen.

  • $varWird nie auf mehrere Wörter erweitert, wird jedoch auf die leere Liste erweitert (im Gegensatz zu einer Liste, die ein einzelnes, leeres Wort enthält), wenn der Wert von vardie leere Zeichenfolge ist. Kontrast:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo
    

    "${array[@]}"Erweitert auf ähnliche Weise alle Elemente des Arrays, während $arraynur die nicht leeren Elemente erweitert werden.

  • Die @Parameter Expansion Flag erfordert manchmal doppelte Anführungszeichen um die ganze Substitution: "${(@)foo}".

  • Bei der Ersetzung von Befehlen wird das Feld geteilt, wenn keine Anführungszeichen gesetzt sind: echo $(echo 'a'; echo '*')prints a *(mit einem einzelnen Leerzeichen), während echo "$(echo 'a'; echo '*')"die nicht geänderte zweizeilige Zeichenfolge gedruckt wird. Verwenden Sie "$(somecommand)"diese Option, um die Ausgabe des Befehls in einem Wort ohne abschließende Zeilenumbrüche zu erhalten. Verwenden Sie "${$(somecommand; echo _)%?}"diese Option, um die genaue Ausgabe des Befehls einschließlich der letzten Zeilenumbrüche abzurufen. Verwenden Sie "${(@f)$(somecommand)}"diese Option , um ein Array von Zeilen aus der Ausgabe des Befehls abzurufen.

Gilles
quelle
Tatsächlich müssen Sie die Anführungszeichen weglassen, damit eine Variable als arithmetischer Ausdruck analysiert wird. Warum kann ich Ihr Beispiel mit Zitaten arbeiten lassen:echo "$(("$expr"))"
Cyker
Dies man bashsagt aus: Der Ausdruck wird so behandelt, als befände er sich in doppelten Anführungszeichen, ein doppeltes Anführungszeichen in Klammern wird jedoch nicht speziell behandelt.
Cyker
4
Auch für jeden, der interessiert ist, die formalen Namen von Split + glob sind einzelne Wörter aufgespalten und Pfadname Expansion .
Cyker
3
Zu Ihrer Information : Bei StackOverflow hat jemand die Sprache "Optional, wenn eine unformatierte Zeichenfolge erwartet wird" in dieser Antwort aktiviert, um zu verhindern, dass ein Argument in Anführungszeichen gesetzt wird echo. Es könnte sich lohnen, die Sprache noch deutlicher zu machen ("Wenn der Parser eine rohe Zeichenfolge erwartet", vielleicht?)
Charles Duffy
2
@CharlesDuffy Ugh, ich hatte nicht an diese Fehlinterpretation gedacht. Ich habe „wo“ in „wann“ geändert und den Satz, wie Sie vorgeschlagen haben, verstärkt.
Gilles