Comment échapper au caractère joker / astérisque dans bash?

136

Par exemple:

me$ FOO="BAR * BAR"
me$ echo $FOO
BAR file1 file2 file3 file4 BAR

et en utilisant le \caractère d'échappement:

me$ FOO="BAR \* BAR"
me$ echo $FOO
BAR \* BAR

Je fais évidemment quelque chose de stupide.

Comment obtenir la sortie BAR * BAR?

Andyuk
la source

Réponses:

139

Citer lorsque le réglage $FOOne suffit pas. Vous devez également citer la référence de la variable:

me$ FOO="BAR * BAR"
me$ echo "$FOO"
BAR * BAR
finnw
la source
8
c'est mystérieux, pourquoi est-ce? que se passe-t-il?
tofutim
Parce que les variables se développent
Daniel
103

RÉPONSE COURTE

Comme d'autres l'ont dit, vous devez toujours citer les variables pour éviter un comportement étrange. Utilisez donc echo "$ foo" au lieu de simplement echo $ foo .

LONGUE RÉPONSE

Je pense que cet exemple mérite des explications supplémentaires, car il se passe plus qu'il n'y paraît à première vue.

Je peux voir d'où vient votre confusion, car après avoir exécuté votre premier exemple, vous vous êtes probablement dit que le shell faisait évidemment:

  1. Expansion des paramètres
  2. Extension du nom de fichier

Donc à partir de votre premier exemple:

me$ FOO="BAR * BAR"
me$ echo $FOO

Après l'expansion des paramètres équivaut à:

me$ echo BAR * BAR

Et après l'extension du nom de fichier, cela équivaut à:

me$ echo BAR file1 file2 file3 file4 BAR

Et si vous tapez simplement echo BAR * BARdans la ligne de commande, vous verrez qu'ils sont équivalents.

Alors vous vous êtes probablement dit "si j'échappe au *, je peux empêcher l'expansion du nom de fichier"

Donc, à partir de votre deuxième exemple:

me$ FOO="BAR \* BAR"
me$ echo $FOO

Après l'expansion des paramètres doit être équivalent à:

me$ echo BAR \* BAR

Et après l'expansion du nom de fichier devrait être équivalent à:

me$ echo BAR \* BAR

Et si vous essayez de taper "echo BAR \ * BAR" directement dans la ligne de commande, cela affichera en effet "BAR * BAR" car l'expansion du nom de fichier est empêchée par l'échappement.

Alors pourquoi l'utilisation de $ foo n'a-t-elle pas fonctionné?

C'est parce qu'il y a une troisième extension qui a lieu - Quote Removal. De la suppression manuelle des devis bash est:

Après les extensions précédentes, toutes les occurrences sans guillemets des caractères '\', '' 'et' "'qui ne résultaient pas de l'une des extensions ci-dessus sont supprimées.

Donc ce qui se passe, c'est que lorsque vous tapez la commande directement dans la ligne de commande, le caractère d'échappement n'est pas le résultat d'une expansion précédente donc BASH le supprime avant de l'envoyer à la commande echo, mais dans le 2ème exemple, le "\ *" était le résultat d'une expansion de paramètre précédente, il n'est donc PAS supprimé. En conséquence, echo reçoit "\ *" et c'est ce qu'il imprime.

Notez la différence entre le premier exemple - "*" n'est pas inclus dans les caractères qui seront supprimés par la suppression de devis.

J'espère que cela a du sens. En fin de compte, la conclusion est la même - utilisez simplement des guillemets. J'ai juste pensé expliquer pourquoi échapper, ce qui devrait logiquement fonctionner si seules les extensions de paramètres et de noms de fichiers sont en jeu, ne fonctionnait pas.

Pour une explication complète des extensions BASH, reportez-vous à:

http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions

mithu
la source
1
Très bonne réponse! Maintenant, je n'ai pas l'impression d'avoir posé une question aussi stupide. :-)
andyuk
1
Existe-t-il un utilitaire pour échapper aux caractères spéciaux?
Jigar Joshi
"vous devriez toujours citer les variables pour éviter un comportement étrange" - lorsque vous voulez les utiliser comme chaînes
Angelo
Bien que la réponse de @finnw ci-dessus réponde directement à la question, cette réponse est bien meilleure pour expliquer le pourquoi , ce qui aide beaucoup plus à extrapoler le fonctionnement de l'utilisation dans nos propres scénarios. C'est le genre de réponse que nous devons voir davantage.
Nicolas Coombs
54

Je vais ajouter un peu à ce vieux fil.

Habituellement, vous utiliseriez

$ echo "$FOO"

Cependant, j'ai eu des problèmes même avec cette syntaxe. Considérez le script suivant.

#!/bin/bash
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"

Les *besoins doivent être transmis mot pour mot curl, mais les mêmes problèmes se poseront. L'exemple ci-dessus ne fonctionnera pas (il s'étendra aux noms de fichiers dans le répertoire actuel) et ni l'un ni l'autre \*. Vous ne pouvez pas non plus citer $curl_optscar il sera reconnu comme une option unique (non valide) de curl.

curl: option -s --noproxy * -O: is unknown
curl: try 'curl --help' or 'curl --manual' for more information

Par conséquent, je recommanderais l'utilisation de la bashvariable $GLOBIGNOREpour empêcher complètement l'expansion du nom de fichier si elle est appliquée au modèle global, ou utilisez l' set -findicateur intégré.

#!/bin/bash
GLOBIGNORE="*"
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"  ## no filename expansion

S'appliquant à votre exemple d'origine:

me$ FOO="BAR * BAR"

me$ echo $FOO
BAR file1 file2 file3 file4 BAR

me$ set -f
me$ echo $FOO
BAR * BAR

me$ set +f
me$ GLOBIGNORE=*
me$ echo $FOO
BAR * BAR
AlexandreH
la source
4
Excellente explication, merci! Mon cas d'utilisation est que SELECT * FROM etc.c'est la seule façon qui fonctionne.
knutole
1
Merci d'avoir présenté la solution set -f!
hachre le
5
FOO='BAR * BAR'
echo "$FOO"
tzot
la source
Cela fonctionne, mais il n'est pas essentiel de changer les guillemets simples de la première ligne en guillemets doubles.
fin
1
Non, ce n'est pas le cas, mais l'habitude d'utiliser des guillemets simples est préférable lorsque vous allez inclure des caractères spéciaux du shell et que vous ne voulez aucune substitution.
tzot le
3

Il vaut peut-être la peine de prendre l'habitude d'utiliser printf plutôt que echosur la ligne de commande.

Dans cet exemple, cela ne donne pas beaucoup d'avantages, mais cela peut être plus utile avec une sortie plus complexe.

FOO="BAR * BAR"
printf %s "$FOO"
Dave Webb
la source
Pourquoi en enfer? printf est un processus séparé (du moins pas un bash intégré) et l'utilisation de printf que vous démontrez n'a aucun avantage sur l'écho.
ddaa le
2
printf est un bash intégré et j'ai dit dans ma réponse "Dans cet exemple, cela ne donne pas beaucoup d'avantages, mais il peut être plus utile avec une sortie plus complexe.
Dave Webb