Warum verwenden Shell-Skriptvergleiche häufig x $ VAR = xyes?

72

Ich sehe dies oft in den Build-Skripten von Projekten, die Autotools verwenden (Autoconf, Automake). Wenn jemand den Wert einer Shell-Variablen überprüfen möchte, verwendet er häufig diese Redewendung:

if test "x$SHELL_VAR" = "xyes"; then
...

Was ist der Vorteil gegenüber der einfachen Überprüfung des Werts wie folgt:

if test $SHELL_VAR = "yes"; then
...

Ich denke, es muss einen Grund geben, warum ich das so oft sehe, aber ich kann nicht herausfinden, was es ist.

Jonner
quelle
2
Siehe auch Shell-Skriptzweck von x in"x$Variable" . Diese Frage ist ein Duplikat dieser Frage.
Jonathan Leffler
2
Siehe auch Bash-Test für leere Zeichenfolge mitX"" . Auch diese Frage ist praktisch ein Duplikat dieser Frage oder deckt den größten Teil des gleichen Grundes ab. Beide Duplikate haben einige gute Antworten.
Jonathan Leffler

Antworten:

92

Wenn Sie eine Shell verwenden, die eine einfache Ersetzung durchführt und die SHELL_VARVariable nicht vorhanden ist (oder leer ist), müssen Sie auf die Randfälle achten. Die folgenden Übersetzungen werden passieren:

if test $SHELL_VAR = yes; then        -->  if test = yes; then
if test x$SHELL_VAR = xyes; then      -->  if test x = xyes; then

Die erste davon erzeugt einen Fehler, da das erste Argument zu testverschwunden ist. Der zweite hat dieses Problem nicht.

Ihr Fall lautet wie folgt:

if test "x$SHELL_VAR" = "xyes"; then  -->  if test "x" = "xyes"; then

Die x, zumindest für POSIX-kompatible Shells, ist tatsächlich redundant, da die Anführungszeichen ergeben, dass sowohl ein leeres Argument als auch ein Argument mit Leerzeichen als ein einzelnes Objekt interpretiert werden.

paxdiablo
quelle
3
Würde das Zitieren den leeren Var-Edge-Fall nicht test "$SHELL_VAR" = yeseleganter lösen als xyes? Wäre eine Änderung der Reihenfolge 'abc' = "$1"für die Optionsverarbeitung eine weitere Option?
Ciro Santilli 法轮功 冠状 病 六四 事件 29
Warum sollte das erste Argument testverschwinden und nicht zu einer leeren Zeichenfolge werden? Behandeln verschiedene Muscheln dies unterschiedlich?
Phk
1
@phk: try export xxx='', if test $xxx = 1 ; then echo 2 ; fi, export xxx='yyy', if test $xxx = 1 ; then echo 2 ; fi. Der erste ifgibt einen Fehler aus, der zweite nicht. Das ist in bash.
Paxdiablo
Mein Fehler, ich habe mit 'printf' getestet, was anscheinend immer davon ausgeht, dass es einen zweiten Parameter gibt.
Phk
1
@SnakE: Die POSIX-kompatiblen Shells machen das sicher, das habe ich in meinem abschließenden Absatz angegeben. Jonathan Lefflers Antwort erklärt, warum das xmöglicherweise in älteren Muscheln verwendet wurde.
Paxdiablo
22

Der andere Grund, den noch niemand erwähnt hat, betrifft die Optionsverarbeitung. Wenn Sie schreiben:

if [ "$1" = "abc" ]; then ...

und $ 1 hat den Wert '-n', die Syntax des Testbefehls ist mehrdeutig; Es ist nicht klar, was Sie getestet haben. Das 'x' an der Vorderseite verhindert, dass ein führender Armaturenbrett Probleme verursacht.

Sie müssen sich wirklich alte Muscheln ansehen, um eine zu finden, bei der der Testbefehl keine Unterstützung für -noder hat -z. Der testBefehl Version 7 (1978) enthielt sie. Es ist nicht ganz irrelevant - einige UNIX-Inhalte der Version 6 sind in BSD entkommen, aber heutzutage ist es äußerst schwierig, etwas zu finden, das im aktuellen Gebrauch so alt ist.

Es ist gefährlich, keine doppelten Anführungszeichen um Werte zu verwenden, wie eine Reihe anderer Personen betonten. In der Tat, wenn die Möglichkeit besteht, dass Dateinamen Leerzeichen enthalten (MacOS X und Windows unterstützen dies in gewissem Maße, und Unix hat dies immer unterstützt, obwohl Tools wiexargserschweren Sie es), dann sollten Sie Dateinamen jedes Mal in doppelte Anführungszeichen setzen, wenn Sie sie auch verwenden. Wenn Sie nicht für den Wert verantwortlich sind (z. B. während der Optionsbehandlung und die Variable beim Start auf "Nein" und beim Einfügen eines Flags in die Befehlszeile auf "Ja" setzen), ist es nicht sicher, nicht zitierte Formen von Variablen zu verwenden bis Sie sie als sicher erwiesen haben - und Sie können es genauso gut die ganze Zeit für viele Zwecke tun. Oder dokumentieren Sie, dass Ihre Skripte schrecklich fehlschlagen, wenn Benutzer versuchen, Dateien mit Leerzeichen in den Namen zu verarbeiten. (Und es gibt noch andere Charaktere, über die man sich Sorgen machen muss - Backticks könnten zum Beispiel auch ziemlich böse sein.)

Jonathan Leffler
quelle
3
Ich würde sagen, es gibt keine Mehrdeutigkeit, wenn der Testkern wie in Ihrem ersten Beispiel richtig zitiert wird. In diesem Fall gibt es 3 Argumente, auch wenn einige Werte leer sind, z. B. ["-n" = ""]. Die testImplementierung sollte daher nicht in den Verzweigungsfall [-n "$ var"] eingehen (für den nur 2 Argumente erforderlich sind). Persönlich gab es jedes Mal, wenn ich Probleme hatte, entweder fehlende Anführungszeichen um $ var, was zu dem Fehler "=: unärer Operator erwartet" führte, oder die Option -z nicht vorhanden oder eine komplexe Verwendung testinnerhalb [...] des Kerns anstelle von mit mehreren [test1] || [test2] auf Shell-Ebene (wie @Jay erwähnt).
user1556814
2
Wenn Sie sich auf moderne Implementierungen verlassen können, können Sie vielleicht damit durchkommen, obwohl es manchmal schwierig ist, Ihre Kommentare zu lesen. In der Vergangenheit haben einige Implementierungen testbizarre Dinge getan, und die Technik war gegen diese bizarren Implementierungen geschützt. Vielleicht haben Sie das Glück, sich heutzutage keine Sorgen mehr machen zu müssen.
Jonathan Leffler
9

Es gibt zwei Gründe, die ich für diese Konvention kenne:

http://tldp.org/LDP/abs/html/comparison-ops.html

In einem zusammengesetzten Test reicht es möglicherweise nicht aus, die Zeichenfolgenvariable in Anführungszeichen zu setzen. [-n "$ string" -o "$ a" = "$ b"] kann bei einigen Versionen von Bash einen Fehler verursachen, wenn $ string leer ist. Der sichere Weg besteht darin, ein zusätzliches Zeichen an möglicherweise leere Variablen anzuhängen, ["x $ string"! = X -o "x $ a" = "x $ b"] (die "x" werden abgebrochen).

Zweitens existierten in anderen Shells als Bash, insbesondere in älteren, die Testbedingungen wie '-z' zum Testen auf eine leere Variable nicht.

if [ -z "$SOME_VAR" ]; then
  echo "this variable is not defined"
fi

funktioniert in BASH einwandfrei. Wenn Sie die Portabilität in verschiedenen UNIX-Umgebungen anstreben, in denen Sie nicht sicher sein können, ob die Standard-Shell Bash ist und ob sie die Testbedingung -z unterstützt, ist es sicherer, das Formular zu verwenden, wenn [" x $ SOME_VAR "=" x "], da dies immer den beabsichtigten Effekt hat. Im Wesentlichen handelt es sich hierbei um einen alten Shell-Scripting-Trick zum Auffinden einer leeren Variablen, der auch heute noch aus Gründen der Abwärtskompatibilität verwendet wird, obwohl sauberere Methoden verfügbar sind.

Jay
quelle
Für den ersten Fall würde ich eigentlich gehen[ -n "$string" ] || [ "$a" = "$b" ]
Weijun Zhou
5

Ich empfehle stattdessen:

if test "yes" = "$SHELL_VAR"; then

da tut es mit dem hässlichen weg xund löst das Problem , indem erwähnt https://stackoverflow.com/a/174288/895245 , die $SHELL_VARmit beginnen können -und als Option gelesen werden.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
2

Ich glaube es liegt an

SHELLVAR=$(true)
if test $SHELLVAR  = "yes" ; then echo "yep" ; fi 

# bash: test: =: unary operator expected

ebenso gut wie

if test $UNDEFINEDED = "yes" ; then echo "yep" ; fi
# bash: test: =: unary operator expected

und

SHELLVAR=" hello" 
if test $SHELLVAR = "hello" ; then echo "yep" ; fi
# yep 

Dies sollte jedoch normalerweise funktionieren

SHELLVAR=" hello"
if test "$SHELLVAR" = "hello" ; then echo "yep" ; fi 
#<no output>

Aber wenn es sich irgendwo anders in der Ausgabe beschwert, ist es schwer zu sagen, worüber es sich beschwert, denke ich

SHELLVAR=" hello"
if test "x$SHELLVAR" = "xhello" ; then echo "yep" ; fi 

funktioniert genauso gut, wäre aber einfacher zu debuggen.

Kent Fredric
quelle
0

Ich habe das unter DOS gemacht, wenn SHELL_VAR möglicherweise undefiniert ist.

Ken
quelle
0

Wenn Sie das "x $ SHELL_VAR" -Ding nicht ausführen, wird bei $ SHELL_VAR eine Fehlermeldung angezeigt, dass "=" kein monadischer Operator oder ähnliches ist.

Paul Tomblin
quelle
Dies gilt nur, wenn Sie nicht richtig zitieren. und wenn Sie nicht richtig zitieren, haben Ihre Shell-Skripte größere Probleme, die durch die Verwendung x$FOOnicht behoben werden können.
Charles Duffy