Faire face à plusieurs niveaux de citation (vraiment, plusieurs niveaux d'analyse / d'interprétation) peut devenir compliqué. Il est utile de garder quelques points à l'esprit:
- Chaque «niveau de cotation» peut potentiellement impliquer une langue différente.
- Les règles de citation varient selon la langue.
- Lorsqu'il s'agit de plus d'un ou de deux niveaux imbriqués, il est généralement plus facile de travailler «de bas en haut» (c'est-à-dire de l'intérieur à l'extérieur).
Niveaux de cotation
Examinons vos exemples de commandes.
pgrep -fl java | grep -i datanode | awk '{print $1}'
Votre premier exemple de commande (ci-dessus) utilise quatre langages: votre shell, le regex dans pgrep , le regex dans grep (qui peut être différent du langage regex dans pgrep ) et awk . Il y a deux niveaux d'interprétation impliqués: le shell et un niveau après le shell pour chacune des commandes impliquées. Il n'y a qu'un seul niveau explicite de citation (citation de shell dans awk ).
ssh host …
Ensuite, vous avez ajouté un niveau de ssh en haut. Il s'agit en fait d'un autre niveau de shell: ssh n'interprète pas la commande elle-même, elle la remet à un shell à l'extrémité distante (via (par exemple) sh -c …
) et ce shell interprète la chaîne.
ssh host "sudo su user -c …"
Ensuite, vous avez demandé d'ajouter un autre niveau de shell au milieu en utilisant su (via sudo , qui n'interprète pas ses arguments de commande, nous pouvons donc l'ignorer). À ce stade, vous avez trois niveaux d'imbrication en cours ( awk → shell, shell → shell ( ssh ), shell → shell ( su user -c ), donc je vous conseille d'utiliser l'approche "bottom, up". Je suppose que vos coquilles sont compatibles avec Bourne (par exemple sh , ash , dash , ksh , bash , zsh , etc.) Un autre type de coquille ( poisson , rc, etc.) peut nécessiter une syntaxe différente, mais la méthode s'applique toujours.
De bas en haut
- Formulez la chaîne que vous souhaitez représenter au niveau le plus intérieur.
- Sélectionnez un mécanisme de citation dans le répertoire de citation de la prochaine langue la plus élevée.
- Citez la chaîne souhaitée en fonction du mécanisme de citation que vous avez sélectionné.
- Il existe souvent de nombreuses variantes sur la façon d'appliquer quel mécanisme de cotation. Le faire à la main est généralement une question de pratique et d'expérience. Lorsque vous le faites par programme, il est généralement préférable de choisir le plus facile à faire (généralement le «plus littéral» (le moins d'échappatoires)).
- Facultativement, utilisez la chaîne entre guillemets résultante avec du code supplémentaire.
- Si vous n'avez pas encore atteint le niveau souhaité de citation / interprétation, prenez la chaîne citée résultante (plus tout code ajouté) et utilisez-la comme chaîne de départ à l'étape 2.
Citer la sémantique varie
La chose à garder à l'esprit ici est que chaque langue (niveau de citation) peut donner une sémantique légèrement différente (ou même une sémantique radicalement différente) au même caractère de citation.
La plupart des langues ont un mécanisme de citation «littéral», mais elles varient exactement dans la mesure où elles sont littérales. La citation simple des shells de type Bourne est en fait littérale (ce qui signifie que vous ne pouvez pas l'utiliser pour citer elle-même un seul caractère de citation). D'autres langages (Perl, Ruby) sont moins littéraux en ce sens qu'ils interprètent certaines séquences de barre oblique inversée à l'intérieur de régions entre guillemets simples de manière non littérale (en particulier, \\
et \'
résultent en \
et '
, mais d'autres séquences de barre oblique inverse sont en réalité littérales).
Vous devrez lire la documentation de chacune de vos langues pour comprendre ses règles de citation et la syntaxe globale.
Votre exemple
Le niveau le plus profond de votre exemple est un programme awk .
{print $1}
Vous allez intégrer cela dans une ligne de commande shell:
pgrep -fl java | grep -i datanode | awk …
Nous devons protéger (au minimum) l'espace et $
dans le awk programme. Le choix évident est d'utiliser des guillemets simples dans le shell autour de l'ensemble du programme.
Il existe cependant d'autres choix:
{print\ \$1}
échapper directement à l'espace et $
{print' $'1}
guillemet simple seulement l'espace et $
"{print \$1}"
citer deux fois le tout et échapper à la $
{print" $"1}
double guillemet uniquement l'espace et $
cela peut plier un peu les règles (non échappé $
à la fin d'une chaîne entre guillemets est littéral), mais il semble fonctionner dans la plupart des shells.
Si le programme utilisait une virgule entre les accolades ouvertes et fermées, nous devions également citer ou échapper à la virgule ou aux accolades pour éviter «l'expansion des accolades» dans certains coques.
Nous le sélectionnons '{print $1}'
et l'intégrons dans le reste du «code» du shell:
pgrep -fl java | grep -i datanode | awk '{print $1}'
Ensuite, vous vouliez l'exécuter via su et sudo .
sudo su user -c …
su user -c …
est juste comme some-shell -c …
(sauf sous un autre UID), donc su ajoute juste un autre niveau de shell. sudo n'interprète pas ses arguments, il n'ajoute donc aucun niveau de citation.
Nous avons besoin d'un autre niveau de shell pour notre chaîne de commande. Nous pouvons à nouveau choisir des guillemets simples, mais nous devons accorder une attention particulière aux guillemets simples existants. La façon habituelle ressemble à ceci:
'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Il y a quatre chaînes ici que le shell interprétera et concaténera: la première chaîne entre guillemets simples ( pgrep … awk
), une citation simple échappée, le programme awk entre guillemets simples, une autre citation unique échappée.
Il existe bien sûr de nombreuses alternatives:
pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1}
échapper à tout ce qui est important
pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}
le même, mais sans espace blanc superflu (même dans le programme awk !)
"pgrep -fl java | grep -i datanode | awk '{print \$1}'"
citez le tout, échappez à la $
'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"
votre variation; un peu plus long que la façon habituelle en raison de l'utilisation de guillemets doubles (deux caractères) au lieu d'échappements (un caractère)
L'utilisation de citations différentes dans le premier niveau permet d'autres variations à ce niveau:
'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
'pgrep -fl java | grep -i datanode | awk {print\ \$1}'
L'incorporation de la première variation dans la ligne de commande sudo / * su * donne ceci:
sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Vous pouvez utiliser la même chaîne dans n'importe quel autre contexte de niveau shell unique (par exemple ssh host …
).
Ensuite, vous avez ajouté un niveau de ssh en haut. Il s'agit en fait d'un autre niveau de shell: ssh n'interprète pas la commande elle-même, mais il la remet à un shell à l'extrémité distante (via (par exemple) sh -c …
) et ce shell interprète la chaîne.
ssh host …
Le processus est le même: prenez la chaîne, choisissez une méthode de citation, utilisez-la, incorporez-la.
Utiliser à nouveau des guillemets simples:
'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
Il y a maintenant onze chaînes qui sont interprétées et concaténées 'sudo su user -c '
:, guillemet 'pgrep … awk '
simple échappé,, guillemet simple échappé, barre oblique inversée échappée, deux guillemets simples échappés, programme awk guillemet simple, guillemet simple échappé, barre oblique inversée échappée et guillemet simple échappé final .
La forme finale ressemble à ceci:
ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
C'est un peu difficile à taper à la main, mais la nature littérale des guillemets simples du shell facilite l'automatisation d'une légère variation:
#!/bin/sh
sq() { # single quote for Bourne shell evaluation
# Change ' to '\'' and wrap in single quotes.
# If original starts/ends with a single quote, creates useless
# (but harmless) '' at beginning/end of result.
printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}
# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }
ap='{print $1}'
s1="pgrep -fl java | grep -i datanode | awk $(sq "$ap")"
s2="sudo su user -c $(sq "$s1")"
ssh host "$(sq "$s2")"
Voir la réponse de Chris Johnsen pour une explication claire et approfondie avec une solution générale. Je vais vous donner quelques conseils supplémentaires qui vous aideront dans certaines circonstances courantes.
Les guillemets simples échappent à tout sauf à un guillemet simple. Donc, si vous savez que la valeur d'une variable n'inclut aucun guillemet simple, vous pouvez l'interpoler en toute sécurité entre guillemets simples dans un script shell.
Si votre shell local est ksh93 ou zsh, vous pouvez gérer les guillemets simples dans la variable en les réécrivant
'\''
. (Bien que bash ait également la${foo//pattern/replacement}
construction, sa gestion des guillemets simples n'a pas de sens pour moi.)Une autre astuce pour éviter d'avoir à traiter des citations imbriquées consiste à passer autant que possible des chaînes dans les variables d'environnement. Ssh et sudo ont tendance à supprimer la plupart des variables d'environnement, mais elles sont souvent configurées pour laisser
LC_*
passer, car elles sont normalement très importantes pour la convivialité (elles contiennent des informations sur les paramètres régionaux) et sont rarement considérées comme sensibles à la sécurité.Ici, puisqu'il
LC_CMD
contient un extrait de shell, il doit être fourni littéralement au shell le plus interne. Par conséquent, la variable est développée par le shell immédiatement au-dessus. La coquille"$LC_CMD"
la plus intérieure mais une voit , et la coquille la plus intérieure voit les commandes.Une méthode similaire est utile pour transmettre des données à un utilitaire de traitement de texte. Si vous utilisez l'interpolation shell, l'utilitaire traitera la valeur de la variable comme une commande, par exemple
sed "s/$pattern/$replacement/"
ne fonctionnera pas si les variables contiennent/
. Utilisez donc awk (pas sed), et soit son-v
option, soit leENVIRON
tableau pour transmettre les données du shell (si vous passez par làENVIRON
, n'oubliez pas d'exporter les variables).la source
Comme Chris Johnson le décrit très bien , vous avez ici quelques niveaux d'indirection cités; vous demandez à votre section locale
shell
d'instruire la télécommandeshell
viassh
qu'il doit indiquersudo
àsu
la télécommandeshell
d'exécuter votre pipeline enpgrep -fl java | grep -i datanode | awk '{print $1}'
tant queuser
. Ce type de commande nécessite beaucoup de travail fastidieux\'"quote quoting"\'
.Si vous suivez mes conseils, vous renoncerez à toutes les absurdités et ferez:
POUR PLUS:
Vérifiez ma réponse (peut-être trop longue) aux chemins de tuyauterie avec différents types de guillemets pour la substitution de barre oblique dans laquelle je discute une partie de la théorie derrière pourquoi cela fonctionne.
-Mike
la source
<<
remplace simplement le premier. Doit-il dire "zsh uniquement" quelque part, ou ai-je raté quelque chose? (Astuce astucieuse, pour avoir un hérédoc qui est en partie soumis à l'expansion locale)Que diriez-vous d'utiliser plus de guillemets doubles?
Ensuite, vous
ssh $host $CMD
devriez très bien travailler avec celui-ci:CMD="pgrep -fl java | grep -i datanode | awk '{print $1}'"
Passons maintenant au plus complexe, le
ssh $host "sudo su user -c \"$CMD\""
. Je suppose que tout ce que vous avez à faire est échapper à caractères sensiblesCMD
:$
,\
et"
. Donc , je vais essayer et voir si cela fonctionne:echo $CMD | sed -e 's/[$\\"]/\\\1/g'
.Si cela semble correct, enveloppez l'écho + sed dans une fonction shell, et vous êtes prêt à continuer
ssh $host "sudo su user -c \"$(escape_my_var $CMD)\""
.la source