3 votes

Comment mettre entre guillemets ou échapper à la totalité de la ligne de commande dans Bash ?

Introduction

Pensez à un outil comme watch qui peut prendre une autre commande (par ex. ls -l ) comme suit :

watch ls -l
# or equivalently
watch 'ls -l'

Si la commande est plus compliquée, c'est une question de guillemets/escaping si des choses comme $variable , * , | , ; o && sont interprétées par le Shell actuel ou vont à watch . Ces deux commandes sont différentes :

watch echo "$$"
watch 'echo "$$"'

Il en va de même pour ces derniers :

watch date ; echo done
watch date \; echo done

ssh est similaire, il peut prendre un ou plusieurs arguments et construire une commande à exécuter sur le serveur. Il dépend de la mise entre guillemets et de l'échappement si le Shell local interprète $variable , | et autres, ou le Shell distant.

Et il y a des commandes comme sh -c qui nécessitent un seul argument contenant du code, mais le besoin de guillemets/escaping est similaire. En effet, les watch (ou ssh ) construit cet argument unique pour sh -c ou une commande similaire.


Problème

J'ai déjà le exactes que je souhaite fournir à watch o ssh ou un outil similaire. La commande est obtenue à partir de l'historique ou collée dans la ligne de commande ou tapée comme si elle allait être exécutée directement ; elle se trouve dans ma ligne de commande mot pour mot. Rien dans la commande doit être développée ou interprétée avant d'arriver à watch ou un outil similaire. En cas de watch cela signifie que l'expansion et l'interprétation doivent avoir lieu plus tard, périodiquement. En cas de ssh cela signifie qu'il doit se produire sur le serveur.

Je dois maintenant faire deux choses :

  • Ajouter watch (ou autre) au début. Ce n'est pas un problème. Je peux le faire en dernier.
  • Citez et/ou échappez la commande originale. En général, la commande peut inclure des guillemets simples, de sorte que l'adoption aveugle des guillemets simples n'est pas une solution. Je pense que je connais la bonne solution générale :

    • remplacer chaque ' con '"'"' ou avec '\'' ,
    • englobe toute la chaîne résultante avec des guillemets simples ;

    mais il est fastidieux et source d'erreurs de le faire à la main.


Question

Puis-je faire en sorte que Bash ajoute correctement un niveau de guillemets simples/escaping à l'ensemble de la ligne de commande à la demande ?

(Note : la question s'est posée alors que je répondais aux questions suivantes celui-ci .)

5voto

Kamil Maciorowski Points 57004

Solution

Oui. Mon idée est d'appeler une fonction Shell (lors d'une frappe au clavier) qui manipulera READLINE_LINE en utilisant le ${variable@Q} caractéristiques.

Parties pertinentes de la documentation :

${parameter@operator}

L'expansion est soit une transformation de la valeur de parameter ou des informations sur parameter lui-même, en fonction de la valeur de operator . Chacun operator est une seule lettre :

Q
L'expansion est une chaîne de caractères qui correspond à la valeur de parameter dans un format qui peut être réutilisé en tant qu'entrée.

( source )

READLINE_LINE
Le contenu de la mémoire tampon de la ligne de lecture, à utiliser avec bind -x [ ] .

( source )

Ce qui suit fonctionne avec Bash 4.4.20 :

_quote_all() { READLINE_LINE="${READLINE_LINE@Q}"; }
bind -x '"\C-x\C-o":_quote_all'

Pour tester la solution, préparez une commande dans la ligne de commande (ne l'exécutez pas), par exemple

d="$(LC_ALL=C date)"; printf 'It'\''s now %s\n' "$d"

(Les citations et l'ensemble du commandement pourraient être simplifiés. C'est délibérément comme ça. Et vous peut exécutez-la pour vous assurer qu'il s'agit d'une commande valide, mais replacez-la dans la ligne de commande avant de poursuivre).

Frapper Ctrl + x , Ctrl + o et elle sera correctement citée/escapée pour nos besoins. Il se présentera comme suit :

'd="$(LC_ALL=C date)"; printf '\''It'\''\'\'''\''s now %s\n'\'' "$d"'

Il ne vous reste plus qu'à ajouter watch (ou ssh … ou autre) devant et exécuter. Si c'est watch puis noter que l'en-tête est du type

Every 2.0s: d="$(LC_ALL=C date)"; printf 'It'\''s now %s\n' "$d"

Il contient la commande originale. La commande a été correctement transmise à watch Aucune partie n'a été interprétée prématurément.


Améliorations

Pour plus de commodité, considérons la variante suivante :

_quote_all() { READLINE_LINE=" ${READLINE_LINE@Q}"; READLINE_POINT=0; }

Il préparera la ligne et placera le curseur au début, de sorte que vous puissiez taper watch immédiatement. Ou peut-être même cette variante (elle porte délibérément un nom différent, nous créons une reliure distincte pour elle) :

_prepend_watch() { READLINE_LINE="watch  ${READLINE_LINE@Q}"; READLINE_POINT=6; }
bind -x '"\C-x\C-w":_prepend_watch'

Ahora Ctrl + x , Ctrl + w poignées de devis, inserts watch automatiquement et place le curseur au bon endroit pour que vous puissiez saisir les options.

Avec une autre fonction utilisant READLINE_POINT il est possible de traiter le scénario suivant : type watch (ou ssh … ) suivi d'une commande, où les guillemets/escapes sont comme si la commande allait être exécutée directement. Placez le curseur à l'endroit où commence la commande, appuyez sur la touche et laissez la fonction modifier tout ce qui se trouve entre le curseur et la fin de la ligne. Je ne fournis pas cette fonction ici ; écrivez-la vous-même si vous en avez besoin.


Yo Dawg, j'ai entendu dire que tu aimais les citations

Vous pouvez empiler la solution. Je veux dire que vous pouvez aller de ceci

df -h | grep -v tmpfs

à cette

watch 'df -h | grep -v tmpfs'

à cette

ssh hostB -t 'watch '\''df -h | grep -v tmpfs'\'''

à cette

ssh -t hostA 'ssh -t hostB '\''watch '\''\'\'''\''df -h | grep -v tmpfs'\''\'\'''\'''\'''

(oui, je sais ssh -J )

en ne frappant que Ctrl + x , Ctrl + o et en ajoutant un ou quelques mots au début de chaque étape.

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X