Expansion de variables à l'intérieur de guillemets simples dans une commande dans Bash

394

Je veux exécuter une commande à partir d'un script bash qui a des guillemets simples et quelques autres commandes à l'intérieur des guillemets simples et une variable.

par exemple repo forall -c '....$variable'

Dans ce format, $est échappé et la variable n'est pas développée.

J'ai essayé les variantes suivantes mais elles ont été rejetées:

repo forall -c '...."$variable" '

repo forall -c " '....$variable' "

" repo forall -c '....$variable' "

repo forall -c "'" ....$variable "'"

Si je remplace la valeur à la place de la variable, la commande est exécutée très bien.

Veuillez me dire où je me trompe.

Rachit
la source
33
repo forall -c ' ...before... '"$variable"' ...after...'
n. «pronoms» m.
2
@nm les guillemets simples font partie de la commande repo. je ne pense pas que cela fonctionnera
Rachit
1
bashmange des guillemets simples. Soit vous n'êtes pas dans bash, soit les guillemets simples ne font pas partie de la repocommande.
n. «pronoms» m.
Je ne suis pas sûr de vous avoir, mais au cas où des guillemets simples devraient être là, ne repo forall -c "'... avant ..." $ variable "... après ...'" travail?
ecv
C'est un script bash exécuté dans le shell bash et la commande repo forall prend les paramètres entre guillemets simples. Si je remplace la valeur réelle, la commande s'exécute correctement.
Rachit

Réponses:

622

Dans les guillemets simples, tout est conservé littéralement, sans exception.

Cela signifie que vous devez fermer les guillemets, insérer quelque chose, puis ressaisir.

'before'"$variable"'after'
'before'"'"'after'
'before'\''after'

La concaténation de mots se fait simplement par juxtaposition. Comme vous pouvez le vérifier, chacune des lignes ci-dessus est un seul mot pour le shell. Les citations (simples ou doubles, selon la situation) n'isolent pas les mots. Ils ne sont utilisés que pour l' interprétation désactiver des différents caractères spéciaux, comme des espaces, $, ;... Pour un bon tutoriel à citer voir la réponse de Mark Reed. Également pertinent: quels caractères doivent être échappés dans bash?

Ne pas concaténer des chaînes interprétées par un shell

Vous devez absolument éviter de construire des commandes shell en concaténant des variables. C'est une mauvaise idée similaire à la concaténation de fragments SQL (injection SQL!).

Il est généralement possible d'avoir des espaces réservés dans la commande et de fournir la commande avec des variables afin que l'appelé puisse les recevoir de la liste des arguments d'appel.

Par exemple, ce qui suit est très dangereux. NE FAITES PAS CELA

script="echo \"Argument 1 is: $myvar\""
/bin/sh -c "$script"

Si le contenu de $myvarn'est pas approuvé, voici un exploit:

myvar='foo"; echo "you were hacked'

Au lieu de l'invocation ci-dessus, utilisez des arguments positionnels. L'invocation suivante est meilleure - elle n'est pas exploitable:

script='echo "arg 1 is: $1"'
/bin/sh -c "$script" -- "$myvar"

Notez l'utilisation de ticks simples dans l'affectation à script, ce qui signifie qu'elle est prise à la lettre, sans expansion variable ou toute autre forme d'interprétation.

Jo So
la source
@ Jo.SO cela ne fonctionnera pas. car la commande repo ne prendra que les paramètres jusqu'à la fermeture des premiers devis. Tout ce qui suit sera ignoré pour le repo et générera une autre erreur.
Rachit
8
@Rachit - le shell ne fonctionne pas comme ça. "some"thing' like'thisest tout un mot à la coquille. Les guillemets ne terminent rien en ce qui concerne l'analyse, et il n'y a aucun moyen pour une commande appelée par le shell de dire ce qui a été cité.
Mark Reed
3
Cette deuxième phrase ("vous ne pouvez même pas échapper aux guillemets simples") fait des guillemets simples les trous noirs du Shell.
@Evert: Je ne sais pas comment le dire mieux. J'ai supprimé la phrase.
Jo So
@JoSo C'est dommage: non la blague tombe à plat.
97

La repocommande ne se soucie pas du type de guillemets qu'elle obtient. Si vous avez besoin d'une extension de paramètre, utilisez des guillemets doubles. Si cela signifie que vous finissez par avoir à inverser beaucoup de choses, utilisez des guillemets simples pour la plupart, puis sortez-en et passez en double pour la partie où vous avez besoin de l'extension.

repo forall -c 'literal stuff goes here; '"stuff with $parameters here"' more literal stuff'

L'explication suit, si vous êtes intéressé.

Lorsque vous exécutez une commande à partir du shell, ce que cette commande reçoit comme arguments est un tableau de chaînes terminées par null. Ces chaînes peuvent contenir absolument tout caractère non nul.

Mais lorsque le shell construit ce tableau de chaînes à partir d'une ligne de commande, il interprète spécialement certains caractères; ceci est conçu pour rendre les commandes plus faciles (en fait, possibles) à taper. Par exemple, les espaces indiquent normalement la frontière entre les chaînes du tableau; pour cette raison, les arguments individuels sont parfois appelés «mots». Mais un argument peut néanmoins contenir des espaces; vous avez juste besoin d'un moyen de dire au shell que c'est ce que vous voulez.

Vous pouvez utiliser une barre oblique inverse devant n'importe quel caractère (y compris l'espace ou une autre barre oblique inverse) pour indiquer au shell de traiter ce caractère littéralement. Mais alors que vous pouvez faire quelque chose comme ça:

echo \"Thank\ you.\ \ That\'ll\ be\ \$4.96,\ please,\"\ said\ the\ cashier

... ça peut devenir ennuyeux. Le shell propose donc une alternative: les guillemets. Ceux-ci se déclinent en deux variétés principales.

Les guillemets doubles sont appelés "guillemets de regroupement". Ils empêchent les caractères génériques et les alias d'être étendus, mais ils visent principalement à inclure des espaces dans un mot. D'autres choses comme l'expansion des paramètres et des commandes (le genre de choses signalées par a $) se produisent toujours. Et bien sûr, si vous voulez un guillemet double littéral à l'intérieur des guillemets doubles, vous devez le contre-obliquer:

echo "\"Thank you. That'll be \$4.96, please,\" said the cashier"

Les guillemets simples sont plus draconiens. Tout entre eux est pris complètement à la lettre, y compris les contre-obliques. Il n'y a absolument aucun moyen d'obtenir un guillemet simple littéral à l'intérieur de guillemets simples.

Heureusement, les guillemets dans le shell ne sont pas des délimiteurs de mots ; par eux-mêmes, ils ne terminent pas un mot. Vous pouvez entrer et sortir des guillemets, y compris entre différents types de guillemets, dans le même mot pour obtenir le résultat souhaité:

echo '"Thank you. That'\''ll be $4.96, please," said the cashier'

C'est donc plus facile - beaucoup moins de barres obliques inverses, bien que la séquence close-single-quote, backslashed-literal-single-quote, open-single-quote prend un certain temps pour s'y habituer.

Les coques modernes ont ajouté un autre style de citation non spécifié par la norme POSIX, dans lequel le guillemet simple de tête est précédé d'un signe dollar. Les chaînes ainsi citées suivent des conventions similaires aux littéraux de chaîne dans le langage de programmation ANSI C, et sont donc parfois appelées "chaînes ANSI" et la paire $'... '"guillemets ANSI". Dans de telles chaînes, les conseils ci-dessus sur les contre-obliques qui sont pris littéralement ne s'appliquent plus. Au lieu de cela, ils redeviennent spéciaux - non seulement vous pouvez inclure un guillemet simple littéral ou une barre oblique inverse en lui ajoutant une barre oblique inverse, mais le shell étend également les échappements de caractères ANSI C (comme \npour une nouvelle ligne, \tpour tabulation et \xHHpour le caractère avec code hexadécimalHH). Sinon, cependant, ils se comportent comme des chaînes entre guillemets simples: aucune substitution de paramètre ou de commande n'a lieu:

echo $'"Thank you.  That\'ll be $4.96, please," said the cashier'

La chose importante à noter est que la chaîne unique reçue comme argument de la echocommande est exactement la même dans tous ces exemples. Une fois que le shell a fini d'analyser une ligne de commande, il n'y a aucun moyen pour la commande en cours d'exécution de dire ce qui a été cité. Même s'il le voulait.

Mark Reed
la source
2
Oui. Je comprends maintenant. Cela fonctionne parfaitement. Merci beaucoup pour votre aide!
Rachit
6
Cette réponse était géniale.
Vinícius Ferrão
1
Le $'string'format POSIX est-il conforme? Aussi, y a-t-il un nom pour cela?
Wildcard
2
@Wildcard $'string'est une extension non-POSIX, que j'ai vue sous le nom de "chaînes ANSI"; J'ai intégré ces faits dans la réponse. Ces chaînes fonctionnent dans la plupart des shells compatibles Bourne modernes: bash, dash, ksh (AT&T et PD) et zsh les prennent tous en charge.
Mark Reed
1
Lien vers l'arrière - Quels caractères doivent être échappés dans les arguments de ligne de commande? sur l'échange de pile Unix et Linux.
Wildcard
3

Voici ce qui a fonctionné pour moi -

QUOTE="'"
hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"
Manmohan
la source
2
J'espère que TBL_HDFS_DIR_PATH n'est pas fourni par l'utilisateur en entrée, cela permettrait une belle injection sql ...
domenukk
1
Mettre QUOTEune variable distincte est complètement superflu; les guillemets simples entre guillemets doubles ne sont qu'un caractère régulier. (Aussi, n'utilisez pas de majuscules pour vos variables privées.)
tripleee
2

EDIT: (Selon les commentaires en question :)

Je me suis penché sur cette question depuis lors. J'ai eu la chance d'avoir un dépôt qui traîne. Il n'est toujours pas clair pour moi si vous devez inclure vos commandes entre guillemets simples par la force. J'ai examiné la syntaxe du référentiel et je ne pense pas que vous en ayez besoin. Vous pouvez utiliser des guillemets doubles autour de votre commande, puis utiliser les guillemets simples et doubles dont vous avez besoin à l'intérieur, à condition d'échapper aux doubles.

ecv
la source
Merci Kevin pour votre aide.
Rachit
2

utilisez simplement printf

au lieu de

repo forall -c '....$variable'

utilisez printf pour remplacer le jeton de variable par la variable développée.

Par exemple:

template='.... %s'

repo forall -c $(printf "${template}" "${variable}")
AndrewD
la source
C'est cassé; printfne vous achète vraiment rien ici et ne pas citer la commande subsititution entraîne de nouveaux problèmes de devis.
tripleee
0

Les variables peuvent contenir des guillemets simples.

myvar=\'....$variable\'

repo forall -c $myvar
user3734617
la source
Ils le peuvent, mais ce n'est pas la bonne façon de le faire - vous devez toujours mettre "$myvar"des guillemets doubles.
tripleee
-2

Est-ce que ça marche pour toi?

eval repo forall -c '....$variable'
drgnfr
la source
8
Est-ce pour vous? Cette question a cinq ans et a déjà des réponses, même acceptées ...
Nico Haase