web-dev-qa-db-fra.com

Expansion de variable entre guillemets simples dans une commande dans Bash

Je souhaite exécuter une commande à partir d'un script shell bash comportant des guillemets simples et d'autres commandes à l'intérieur de ces guillemets simples et d'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 substitue la valeur à la place de la variable, la commande est exécutée correctement.

S'il vous plaît, dites-moi où je me trompe.

269
Rachit

À l'intérieur des guillemets simples, tout est conservé littéralement, sans exception.

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

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

Comme vous pouvez le vérifier, chacune des lignes ci-dessus correspond à un seul mot dans le shell. La concaténation de chaînes se fait simplement par juxtaposition. Les guillemets (guillemets simples ou doubles, selon la situation) servent à désactiver l'interprétation de divers caractères spéciaux, tels que les espaces, le $, le ;... Vous trouverez un bon tutoriel sur les guillemets dans Mark Reed. Aussi pertinent: Quels personnages doivent être échappés dans bash?

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

Vous devez absolument éviter de créer 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 à partir 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 $myvar n'est pas approuvé, voici un exploit:

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

Au lieu de l'invocation ci-dessus, utilisez des arguments de position. 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 ni autre forme d’interprétation.

459
Jo So

La commande repo ne se soucie pas du type de citations qu’elle obtient. Si vous avez besoin de développer les paramètres, utilisez des guillemets doubles. Si cela signifie que vous finissez par avoir à supprimer beaucoup de choses, utilisez des guillemets simples pour la plupart, puis sortez-les et passez en doubles pour la partie où vous avez besoin de l'expansion.

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

Explication suit, si cela vous intéresse.

Lorsque vous exécutez une commande à partir du shell, ce qu'elle reçoit sous forme d'arguments est un tableau de chaînes terminées par un caractère null. Ces chaînes peuvent contenir absolument n'importe quel caractère non-null.

Mais lorsque le shell construit ce tableau de chaînes à partir d'une ligne de commande, il interprète certains caractères de manière spéciale. ceci est conçu pour rendre les commandes plus faciles (en effet, possibles) à taper. Par exemple, les espaces indiquent normalement la limite 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 à Shell que c'est ce que vous voulez.

Vous pouvez utiliser une barre oblique inverse devant tout 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 fastidieux. Donc, le Shell offre une alternative: les guillemets. Ceux-ci viennent 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 développés, mais ils consistent principalement à inclure des espaces dans un mot. D'autres choses, comme l'expansion des paramètres et des commandes (le genre de choses signalé par un $), se produisent toujours. Et bien sûr, si vous voulez une double citation littérale entre guillemets doubles, vous devez la supprimer:

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 barres obliques inverses. Il n'y a absolument aucun moyen d'obtenir une citation unique littérale entre 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 (ou entre différents types de guillemets) au sein d'un 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 ouverte contenant une citation unique, une citation inversée littérale, une citation unique, une citation simple nécessite un certain temps d'adaptation.

Les shells modernes ont ajouté un autre style de citation non spécifié par le standard POSIX, dans lequel le guillemet simple en tête est précédé du signe dollar. Les chaînes ainsi citées suivent des conventions similaires aux littéraux de chaîne du langage de programmation ANSI C et sont donc parfois appelées "chaînes ANSI" et le couple $'...' "ANSI quotes". Au sein de telles chaînes, le conseil ci-dessus concernant les barres obliques inversées prises à la lettre ne s'applique plus. Au lieu de cela, ils redeviennent spéciaux - vous pouvez non seulement inclure un guillemet ou une barre oblique inverse en faisant précéder une barre oblique inverse, mais le shell développe également les caractères d'échappement ANSI C (tels que \n pour une nouvelle ligne, \t pour une tabulation et \xHH pour le caractère avec le code hexadécimal HH). Sinon, 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'

Il est important de noter que la chaîne unique reçue en tant qu'argument de la commande echo est exactement la même chose dans tous ces exemples. Une fois que le shell a analysé une ligne de commande, il n’existe aucun moyen pour la commande en cours d’exécuter ce qui a été cité. Même s'il le voulait.

76
Mark Reed

EDIT: (Selon les commentaires en question :)

J'ai étudié cela depuis. J'ai eu la chance de pouvoir compter sur repo. Je ne sais toujours pas si vous devez forcer vos commandes entre guillemets simples. J'ai examiné la syntaxe des pensions et je ne pense pas que vous ayez à le faire. 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'éviter les guillemets doubles.

2
Kevin Remo

Voici ce qui a fonctionné pour moi -

QUOTE="'"
Hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"
1
Manmohan

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}")
0
AndrewD

Les variables peuvent contenir des guillemets simples.

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

repo forall -c $myvar
0
user3734617