Comment puis-je développer une variable entre guillemets si elle est vide?

21

Disons que j'ai un script en train de faire:

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

Et, dans le cas qui var1est vide, je préfère qu'il soit remplacé par rien au lieu de la chaîne vide, de sorte que la commande exécutée soit:

some-command "$var2" ...

et pas:

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

Existe-t-il un moyen plus simple que de tester la variable et de l'inclure conditionnellement?

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

Y a-t-il une substitution de paramètres qui peut s'étendre à rien en bash, zsh ou similaire? Je pourrais toujours vouloir utiliser la globalisation dans le reste des arguments, donc désactiver cela et ne pas citer la variable n'est pas une option.

muru
la source
Je savais que j'avais déjà vu et probablement utilisé cela auparavant, mais cela s'est avéré difficile à rechercher. Maintenant que Michael a montré la syntaxe, je me suis souvenu où je l'avais vu assez rapidement: unix.stackexchange.com/a/269549/70524 , unix.stackexchange.com/q/68484/70524
muru
Si vous savez qu'il s'agit d'une sorte de substitution de paramètres, pourquoi n'avez-vous pas examiné la section d' extension des paramètres de la manpage? (-;
Philippos
1
@Philippos Je ne savais pas ce que c'était à l'époque, seulement que je l'avais vu ou utilisé auparavant. Connus connus et inconnus. :(
muru
1
des cookies supplémentaires pour mentionner l'utilisation d'un tableau pour contenir les arguments dans la question elle-même.
ilkkachu

Réponses:

29

Les coques et Bash conformes à Posix ont${parameter:+word} :

Si le paramètre est non défini ou nul, null doit être remplacé; sinon, l'expansion de mot (ou une chaîne vide si mot est omis) doit être remplacée.

Vous pouvez donc simplement faire:

${var1:+"$var1"}

et doivent var1être vérifiées et "$var1"utilisées si elles sont définies et non vides (avec les règles ordinaires de guillemet double). Sinon, il se développe à rien. Notez que seule la partie intérieure est citée ici, pas le tout.

La même chose fonctionne également dans zsh. Vous devez répéter la variable, donc ce n'est pas idéal, mais cela fonctionne exactement comme vous le vouliez.

Si vous souhaitez qu'une variable définie mais vide se développe en un argument vide, utilisez ${var1+"$var1"}plutôt.

Michael Homer
la source
1
Suffisant pour moi. Donc, en cela, le tout n'est pas cité, seule la wordpartie l'est.
muru
1
Comme la question demandait "bash, zsh, ou similaire" (et pour l'archive Q&A), j'ai édité pour refléter qu'il s'agit d'une fonctionnalité posix. Même si vous ajoutez une balise / bash à la question après mon commentaire. (-;
Philippos
2
J'ai pris la liberté d'éditer la différence entre :+et +.
ilkkachu
Merci beaucoup pour une solution compatible POSIX! J'essaie d'utiliser sh/ dashpour des scripts de chokepoint potentiels, donc j'apprécie toujours quand quelqu'un montre comment une chose est possible sans recourir à Bash.
JamesTheAwesomeDude
Je cherchais l'effet inverse (développer à autre chose s'il commence comme nul). Il s'avère que cette logique peut être inversée en changeant le + en a - comme dans: $ {empty_var: -replacement}
Alex Jansen
5

C'est ce que zshfait par défaut lorsque vous omettez les guillemets:

some-command $var1 $var2

En fait, la seule raison pour laquelle vous avez toujours besoin de guillemets en zsh autour de l'expansion des paramètres est d'éviter ce comportement (la suppression vide) car il zshn'a pas les autres problèmes qui affectent les autres shells lorsque vous ne citez pas les extensions de paramètres (la séparation implicite + glob) .

Vous pouvez faire de même avec d'autres shells de type POSIX si vous désactivez le split et le glob:

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

Maintenant, je dirais que si votre variable peut avoir une valeur de 0 ou 1, ce devrait être un tableau et non une variable scalaire et utiliser:

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

Et utilisez var1=(value)quand var1doit contenir une valeur, var1=('')quand il doit contenir une valeur vide et var1=()quand il ne doit contenir aucune valeur.

Stéphane Chazelas
la source
0

J'ai rencontré cela en utilisant rsync dans un script bash qui a démarré la commande avec ou sans -nbasculer les exécutions à sec. Il s'avère que rsync et un certain nombre de commandes gnu prennent ''comme premier argument valide et agissent différemment que s'il n'y était pas.

Cela a pris un certain temps à déboguer car les paramètres nuls sont presque complètement invisibles.

Quelqu'un sur la liste rsync m'a montré un moyen d'éviter ce problème tout en simplifiant grandement mon codage. Si je comprends bien, c'est une variation de la dernière suggestion de @ Stéphane Chazelas.

Créez vos arguments de commande dans un certain nombre de variables distinctes. Ceux-ci peuvent être définis dans n'importe quel ordre ou logique qui convient au problème.

Ensuite, à la fin, utilisez les variables pour construire un tableau avec tout à sa place et utilisez-le comme arguments de la commande réelle.

De cette façon, la commande n'est émise qu'à un seul endroit dans le code au lieu d'être répétée pour chaque variation des arguments.

Toute variable vide disparaît simplement à l'aide de cette méthode.

Je sais que l'utilisation d'eval est très mal vue. Je ne me souviens pas de tous les détails, mais il me semblait en avoir besoin pour que les choses fonctionnent de cette façon - quelque chose à voir avec la gestion des paramètres avec un espace blanc intégré.

Exemple:

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
la source
C'est une pire façon de faire ce que j'ai décrit dans la question (construire un tableau d'arguments conditionnellement). En laissant les variables non citées, qui sait à quels problèmes vous vous laissez ouvert.
muru
@muru Vous avez raison, mais j'ai encore besoin de quelque chose comme ça. Je ne vois pas vraiment comment le réparer en utilisant les techniques des autres réponses. Ce serait formidable de voir un fragment de code fait de la bonne manière qui le rassemble. Je vais jouer avec la dernière option de Stéphane plus tard car cela pourrait faire ce que je veux.
Joe
Comme paste.ubuntu.com/26383847 ?
muru le
Je reviens à ce sujet. Merci pour l'exemple. Ca a du sens. Je vais travailler avec ça.
Joe
J'ai un cas d'utilisation similaire ici, mais vous pouvez faire quelque chose comme ceci: if ["$ dry" == "true"]; puis sec = "- n"; fi ... donc si la variable dry est vraie, vous la faites être -n, puis vous construisez votre commande rsync comme d'habitude et vous l'avez comme ceci: rsync $ {dry1: + "$ dry1"} ... si elle est nulle rien ne se passera, sinon il deviendra -n dans votre commande
Freedo