Wie kann ich eine in Anführungszeichen gesetzte Variable zu nichts erweitern, wenn sie leer ist?

21

Angenommen, ich habe ein Skript:

some-command "$var1" "$var2" ...

Und für den Fall, dass var1es leer ist, würde ich es vorziehen, es durch nichts anstelle der leeren Zeichenfolge zu ersetzen, so dass der ausgeführte Befehl lautet:

some-command "$var2" ...

und nicht:

some-command '' "$var2" ...

Gibt es einen einfacheren Weg, als die Variable zu testen und bedingt einzuschließen?

if [ -n "$1" ]; then
    some-command "$var1" "$var2" ...
    # or some variant using arrays to build the command
    # args+=("$var1")
else
    some-command "$var2" ...
fi

Gibt es eine Parametersubstitution, die in bash, zsh oder dergleichen zu nichts erweitert werden kann? Möglicherweise möchte ich in den restlichen Argumenten immer noch Globbing verwenden. Das Deaktivieren und Aufheben der Anführungszeichen für die Variable ist daher keine Option.

muru
quelle
Ich wusste, dass ich das schon einmal gesehen und wahrscheinlich auch benutzt hatte, aber es erwies sich als schwierig zu suchen. Nachdem Michael die Syntax gezeigt hatte, erinnerte ich mich, wo ich sie zuerst schnell genug gesehen hatte: unix.stackexchange.com/a/269549/70524 , unix.stackexchange.com/q/68484/70524
muru
Wenn Sie wissen, dass es sich um eine Art Parameterersetzung handelt, warum haben Sie den Abschnitt zur Parametererweiterung auf der manSeite nicht gelesen? (-;
Philippos
1
@Philippos Ich wusste nicht, was es zu der Zeit war, nur dass ich es vorher gesehen oder benutzt hatte. Bekannte und unbekannte Bekannte. :(
muru
1
Zusätzliche Cookie-Punkte für die Erwähnung der Verwendung eines Arrays, um die Argumente in der Frage selbst zu speichern.
Ilkkachu

Antworten:

29

Posix-konforme Shells und Bash haben ${parameter:+word} :

Wenn der Parameter nicht gesetzt oder null ist, wird null ersetzt. Andernfalls wird die Erweiterung des Wortes (oder eine leere Zeichenfolge, wenn das Wort weggelassen wird) ersetzt.

Sie können also einfach Folgendes tun:

${var1:+"$var1"}

und müssen var1überprüft und verwendet "$var1"werden, wenn sie gesetzt und nicht leer sind (mit den üblichen Regeln für doppelte Anführungszeichen). Sonst dehnt es sich zu nichts aus. Beachten Sie, dass hier nur der innere Teil und nicht das Ganze zitiert wird.

Das gleiche funktioniert auch in zsh. Sie müssen die Variable wiederholen, es ist also nicht ideal, aber es funktioniert genau so, wie Sie es wollten.

Wenn Sie möchten, dass eine Variable mit festem aber leerem Inhalt zu einem leeren Argument erweitert wird, verwenden Sie ${var1+"$var1"}stattdessen.

Michael Homer
quelle
1
Gut genug für mich. Hier wird also nicht das Ganze zitiert, sondern nur der wordTeil.
Muru
1
Als die Frage nach "bash, zsh oder ähnlichem" (und für das Q & A-Archiv) gestellt wurde, habe ich sie bearbeitet, um zu reflektieren, dass dies eine Posix-Funktion ist. Auch wenn Sie der Frage nach meinem Kommentar ein / bash-Tag hinzufügen. (-;
Philippos
2
Ich habe mir erlaubt, den Unterschied zwischen :+und zu ändern +.
ilkkachu
Vielen Dank für eine POSIX-konforme Lösung! Ich versuche, sh/ dashfür potenzielle Chokepoint-Skripte zu verwenden, daher schätze ich es immer, wenn jemand zeigt, wie etwas möglich ist, ohne auf Bash zurückzugreifen.
JamesTheAwesomeDude
Ich habe nach dem gegenteiligen Effekt gesucht (auf etwas anderes erweitern, wenn er mit null beginnt). Es stellt sich heraus, dass diese Logik umgekehrt werden kann, indem das + in ein - geändert wird, wie in: $ {empty_var: -replacement}
Alex Jansen,
5

Dies zshgeschieht standardmäßig, wenn Sie die Anführungszeichen weglassen:

some-command $var1 $var2

Eigentlich ist der einzige Grund , warum Sie noch brauchen Anführungszeichen in zsh um Parameter Expansion ist dieses Verhalten zu vermeiden (die leere Entfernung) als zshnicht über die anderen Probleme , die anderen Schalen beeinflussen , wenn Sie keine Parameter Erweiterungen zitieren (die impliziten Split + glob) .

Sie können dasselbe mit anderen POSIX-ähnlichen Shells tun, wenn Sie split und glob deaktivieren:

(IFS=; set -o noglob; some-command $var1 $var2)

Nun würde ich argumentieren, dass wenn Ihre Variable entweder 0 oder 1 Wert haben kann, es ein Array und keine skalare Variable sein sollte und verwenden Sie:

some-command "${var1[@]}" "${var2[@]}"

Verwenden Sie, var1=(value)wenn var1ein Wert enthalten sein soll, var1=('')wenn ein leerer Wert enthalten sein soll und var1=()wenn kein Wert enthalten sein soll.

Stéphane Chazelas
quelle
0

Ich bin mit rsync in einem Bash-Skript darauf gestoßen, das den Befehl mit oder ohne -nUmschalten zwischen Testläufen gestartet hat . Es stellt sich heraus, dass rsync und eine Reihe von Gnu-Befehlen ausgeführt werden'' als gültiges erstes Argument gelten und sich anders als wenn sie nicht vorhanden wären.

Das Debuggen dauerte eine Weile, da Null-Parameter fast unsichtbar sind.

Jemand auf der rsync-Liste zeigte mir, wie ich dieses Problem vermeiden und gleichzeitig meine Codierung erheblich vereinfachen kann. Wenn ich es richtig verstehe, ist dies eine Variation des letzten Vorschlags von @ Stéphane Chazelas.

Erstellen Sie Ihre Befehlsargumente in mehreren separaten Variablen. Diese können in einer beliebigen Reihenfolge oder Logik festgelegt werden, die dem Problem entspricht.

Verwenden Sie dann am Ende die Variablen, um ein Array zu erstellen, in dem sich alles an der richtigen Stelle befindet, und verwenden Sie dies als Argumente für den tatsächlichen Befehl.

Auf diese Weise wird der Befehl nur an einer Stelle im Code ausgegeben, anstatt für jede Variation der Argumente wiederholt zu werden.

Jede leere Variable verschwindet mit dieser Methode.

Ich weiß, dass das Verwenden von eval sehr verpönt ist. Ich erinnere mich nicht an alle Details, aber es schien notwendig zu sein, damit die Dinge auf diese Weise funktionieren - etwas, das mit dem Umgang mit Parametern mit eingebettetem Leerraum zu tun hat.

Beispiel:

dry_run=''
if [[ it is a test run ]]
then
  dry_run='-n'
fi
...
rsync_options=(
  ${dry_run}
  -avushi
  ${delete}
  ${excludes}
  --stats
  --progress
)
...
eval rsync "${rsync_options[@]}" ...
Joe
quelle
Das ist eine schlechtere Methode, um das zu tun, was ich in der Frage beschrieben habe (eine Reihe von Argumenten unter bestimmten Bedingungen aufbauen). Indem Sie die Variablen nicht angeben, wer weiß, für welche Probleme Sie sich offen halten.
muru
@muru Du hast recht, aber ich brauche noch so etwas. Ich verstehe nicht ganz, wie ich es mithilfe der Techniken aus den anderen Antworten beheben kann. Es wäre großartig, ein Codefragment so zu sehen, dass es alles zusammenfügt. Ich werde später mit Stephanes letzter Option spielen, da dies tun könnte, was ich will.
Joe
Ich komme gerade darauf zurück. Danke für das Beispiel. Es macht Sinn. Ich werde damit arbeiten.
Joe
Ich habe hier einen ähnlichen Anwendungsfall, aber Sie könnten so etwas tun: if ["$ dry" == "true"]; dann trocken = "- n"; fi ... Wenn also die Variable dry wahr ist, setzen Sie sie auf -n und erstellen dann wie gewohnt Ihren Befehl rsync: rsync $ {dry1: + "$ dry1"} ... wenn er null ist nichts wird passieren, sonst wird es -n in Ihrem Befehl werden
Freedo