Est-il possible dans un shell bash interactif d'entrer une commande qui génère du texte afin qu'il apparaisse à la prochaine invite de commande, comme si l'utilisateur avait saisi ce texte à cette invite?
Je veux pouvoir source
créer un script qui générera une ligne de commande et la affichera afin qu'elle apparaisse lorsque l'invite revient après la fin du script, de sorte que l'utilisateur puisse éventuellement le modifier avant d'appuyer sur enter
pour l'exécuter.
Cela peut être réalisé avec, xdotool
mais cela ne fonctionne que lorsque le terminal est dans une fenêtre X et uniquement s’il est installé.
[me@mybox] 100 $ xdotool type "ls -l"
[me@mybox] 101 $ ls -l <--- cursor appears here!
Cela peut-il être fait en utilisant uniquement bash?
Réponses:
Avec
zsh
, vous pouvez utiliserprint -z
du texte dans la mémoire tampon de l’éditeur de lignes pour la prochaine invite:amorcerait l’éditeur de ligne avec
echo test
lequel vous pourrez éditer à l’invite suivante.Je ne pense pas qu’il
bash
existe une fonctionnalité similaire, mais sur de nombreux systèmes, vous pouvez amorcer le tampon d’entrée du terminal avecTIOCSTI
ioctl()
:Insérerait
echo test
dans le tampon d'entrée du terminal, comme s'il avait été reçu du terminal.Une variante plus portable de l'
Terminology
approche de @ mike et qui ne sacrifie pas la sécurité consisterait à envoyer à l'émulateur de terminal unequery status report
séquence d'échappement assez standard : les<ESC>[5n
terminaux répondent invariablement (en tant qu'entrée)<ESC>[0n
et sont liés à la chaîne que vous souhaitez insérer:Si
screen
vous êtes dans GNU , vous pouvez également faire:Désormais, à l'exception de l'approche TIOCSTI ioctl, nous demandons à l'émulateur de terminal de nous envoyer une chaîne comme si elle était typée. Si cette chaîne précède
readline
(bash
l'éditeur de ligne) a désactivé l'écho local du terminal, cette chaîne ne sera pas affichée à l'invite du shell, ce qui perturbera légèrement l'affichage.Pour contourner ce problème, vous pouvez retarder légèrement l'envoi de la demande au terminal afin de vous assurer que la réponse parvient lorsque l'écho a été désactivé par readline.
(ici en supposant que votre
sleep
supporte une résolution inférieure à la seconde).Idéalement, vous voudriez faire quelque chose comme:
Cependant
bash
(contrairement àzsh
) ne supporte pas un telwait-until-the-response-arrives
qui ne lit pas la réponse.Cependant, il a une
has-the-response-arrived-yet
fonctionnalité avecread -t0
:Lectures complémentaires
Voir la réponse de @ starfry qui développe les deux solutions proposées par @mikeserv et moi-même avec quelques informations plus détaillées.
la source
bind '"\e[0n": "echo test"'; printf '\e[5n'
probablement la réponse uniquement bash que je cherche. Ça marche pour moi. Cependant, je suis également^[[0n
imprimé avant mon invite. J'ai découvert que c'est causé quand$PS1
contient un sous-shell. Vous pouvez le reproduire en le faisantPS1='$(:)'
avant la commande bind. Pourquoi cela arriverait-il et peut-on faire quelque chose à ce sujet?\r
chiffre à la tête de$PS1
? Cela devrait fonctionner si$PS1
est assez long. Si non, alors mettez-^[[M
y.r
fait le tour. Cela n'empêche évidemment pas la sortie, elle est simplement écrasée avant que l'œil ne la voie. J'imagine que^[[M
la ligne efface le texte injecté au cas où il serait plus long que l'invite. Est-ce exact (je ne l'ai pas trouvée dans la liste d'échappement ANSI que j'ai)?Cette réponse est fournie à titre de clarification de ma propre compréhension et est inspirée par @ StéphaneChazelas et @mikeserv avant moi.
TL; DR
bash
sans aide extérieure;ioctl
maisbash
solution la plus facile à utiliser utilisebind
.La solution facile
Bash a un shell intégré appelé
bind
qui permet à une commande shell d’être exécutée lorsqu’une séquence de touches est reçue. En substance, la sortie de la commande shell est écrite dans le tampon d'entrée du shell.La séquence de touches
\e[0n
(<ESC>[0n
) est un code d'échappement de terminal ANSI envoyé par un terminal pour indiquer qu'il fonctionne normalement. Il envoie ceci en réponse à une demande de rapport d'état de périphérique qui est envoyée en tant que<ESC>[5n
.En liant la réponse à une
echo
sortie du texte à injecter, nous pouvons injecter ce texte à tout moment en demandant l'état du périphérique, et cela en envoyant une<ESC>[5n
séquence d'échappement.Cela fonctionne et est probablement suffisant pour répondre à la question initiale car aucun autre outil n'est impliqué. C'est pur
bash
mais repose sur un terminal qui se comporte bien (pratiquement tous le sont).Il laisse le texte renvoyé sur la ligne de commande prêt à être utilisé comme s'il avait été saisi. Vous pouvez l'ajouter, le modifier et appuyer sur
ENTER
pour l'exécuter.Ajoutez
\n
à la commande liée pour l'exécuter automatiquement.Cependant, cette solution ne fonctionne que dans le terminal actuel (ce qui relève de la question d'origine). Cela fonctionne à partir d'une invite interactive ou d'un script source mais il génère une erreur s'il est utilisé depuis un sous-shell:
La solution correcte décrite ci-après est plus flexible, mais elle repose sur des commandes externes.
La bonne solution
La méthode appropriée pour injecter une entrée utilise tty_ioctl , un appel système Unix pour le contrôle d'E / S comportant une
TIOCSTI
commande permettant d'injecter une entrée.TIOC de " T erminal CIO tl " et STI de " S fin T erminal I nput ".
Il n'y a pas de commande intégrée
bash
pour cela; cela nécessite une commande externe. Il n’existe pas de commande de ce type dans la distribution GNU / Linux typique, mais ce n’est pas difficile à réaliser avec un peu de programmation. Voici une fonction shell qui utiliseperl
:Voici
0x5412
le code de laTIOCSTI
commande.TIOCSTI
est une constante définie dans les fichiers d'en-tête C standard avec la valeur0x5412
. Essayezgrep -r TIOCSTI /usr/include
, ou regardez dedans/usr/include/asm-generic/ioctls.h
; il est inclus dans les programmes C indirectement par#include <sys/ioctl.h>
.Vous pouvez alors faire:
Les implémentations dans d’autres langues sont présentées ci-dessous (sauvegardées dans un fichier puis dans un fichier
chmod +x
):Perl
inject.pl
Vous pouvez générer
sys/ioctl.ph
ce qui définitTIOCSTI
au lieu d'utiliser la valeur numérique. Voir iciPython
inject.py
Rubis
inject.rb
C
inject.c
compiler avec
gcc -o inject inject.c
**! ** Il y a d'autres exemples ici .
Utiliser
ioctl
pour faire cela fonctionne dans les sous-coques. Il peut également s'injecter dans d'autres terminaux, comme expliqué ci-après.Aller plus loin (contrôler d'autres terminaux)
Cela dépasse le cadre de la question initiale, mais il est possible d'injecter des caractères dans un autre terminal, sous réserve de disposer des autorisations appropriées. Normalement, cela signifie être
root
, mais voir ci-dessous pour d'autres moyens.L'extension du programme C donné ci-dessus pour accepter un argument de ligne de commande spécifiant le terminal d'un autre terminal permet d'injecter dans ce terminal:
Il envoie également une nouvelle ligne par défaut, mais, similaire à
echo
, il offre une-n
option pour le supprimer. L' option--t
ou--tty
nécessite un argument -tty
le terminal à injecter. La valeur pour cela peut être obtenue dans ce terminal:Compilez-le avec
gcc -o inject inject.c
. Préfixez le texte à injecter--
s'il contient des traits d'union afin d'éviter que l'analyseur d'arguments interprète mal les options de ligne de commande. Voir./inject --help
. Utilisez-le comme ceci:ou juste
injecter le terminal courant.
L'injection dans un autre terminal nécessite des droits d'administration pouvant être obtenus par:
root
,sudo
,CAP_SYS_ADMIN
capacité ousetuid
Pour assigner
CAP_SYS_ADMIN
:Pour assigner
setuid
:Sortie propre
Le texte injecté apparaît avant l'invite comme s'il avait été tapé avant l'invite (ce qui était le cas), mais il réapparaît ensuite après l'invite.
Une façon de masquer le texte qui apparaît avant l'invite consiste à l'ajouter à la fin avec un retour à la
\r
ligne ( pas de saut de ligne) et à effacer la ligne actuelle (<ESC>[M
):Cependant, cela effacera uniquement la ligne sur laquelle l'invite apparaît. Si le texte injecté inclut des nouvelles lignes, cela ne fonctionnera pas comme prévu.
Une autre solution désactive l'écho des caractères injectés. Un wrapper utilise
stty
pour faire ceci:où
inject
est l’une des solutions décrites ci-dessus ou remplacée parprintf '\e[5n'
.Approches alternatives
Si votre environnement remplit certaines conditions préalables, vous pouvez disposer d'autres méthodes que vous pouvez utiliser pour injecter des entrées. Si vous êtes dans un environnement de bureau, xdotool est un utilitaire X.Org qui simule l’activité de la souris et du clavier, mais votre distribution risque de ne pas l’inclure par défaut. Tu peux essayer:
Si vous utilisez tmux , le multiplexeur de terminaux, vous pouvez procéder comme suit :
où
-t
sélectionne la session et le volet à injecter. GNU Screen a une capacité similaire avec sastuff
commande:Si votre distribution inclut le paquet console-tools , alors vous pouvez avoir une
writevt
commande qui utiliseioctl
comme nos exemples. Cependant, la plupart des distributions ont déconseillé ce package au profit de kbd, qui manque de cette fonctionnalité.Une copie mise à jour de writevt.c peut être compilée avec
gcc -o writevt writevt.c
.Parmi les autres options pouvant convenir à certains cas d'utilisation, on peut citer les options expect et empty, conçues pour permettre la scriptage d'outils interactifs.
Vous pouvez également utiliser un shell qui prend en charge l’injection terminale, comme
zsh
ce que vous pouvez faireprint -z ls
.La réponse "Wow, c'est intelligent ..."
La méthode décrite ici est également discutée ici et repose sur la méthode discutée ici .
Une redirection de shell à partir d'
/dev/ptmx
un nouveau pseudo-terminal:Un petit outil écrit en C qui déverrouille le maître pseudoterminal (ptm) et renvoie le nom de l'esclave pseudoterminal (pts) sur sa sortie standard.
(enregistrer sous
pts.c
et compiler avecgcc -o pts pts.c
)Lorsque le programme est appelé avec son entrée standard définie sur un ptm, il déverrouille les points correspondants et affiche son nom sur la sortie standard.
La fonction unlockpt () déverrouille le périphérique pseudoterminal esclave correspondant au pseudoterminal maître auquel le descripteur de fichier donné fait référence. Le programme passe ceci à zéro, ce qui correspond à l' entrée standard du programme .
La fonction ptsname () renvoie le nom du périphérique pseudoterminal esclave correspondant au maître référencé par le descripteur de fichier donné, en passant à nouveau à zéro pour l'entrée standard du programme.
Un processus peut être connecté aux pts. Commencez par obtenir un ptm (ici, il est assigné au descripteur de fichier 3, ouvert en lecture-écriture par la
<>
redirection).Puis démarrez le processus:
Les processus générés par cette ligne de commande sont mieux illustrés avec
pstree
:La sortie est relative au shell actuel (
$$
) et les PID (-p
) et PGID (-g
) de chaque processus sont indiqués entre parenthèses(PID,PGID)
.Le
bash(5203,5203)
shell interactif dans lequel nous tapons les commandes et ses descripteurs de fichier le connectent au terminal que nous utilisons pour interagir avec lui (xterm
, ou similaire).En regardant à nouveau la commande, le premier ensemble de parenthèses a démarré un sous-shell,
bash(6524,6524)
avec son descripteur de fichier 0 (son entrée standard ) attribué aux pts (ouvert en lecture-écriture<>
), renvoyé par un autre sous-shell ayant exécuté./pts <&3
le déverrouillage du fichier. pts associés au descripteur de fichier 3 (créé à l'étape précédenteexec 3<>/dev/ptmx
).Le descripteur de fichier 3 du sous-shell est fermé (
3>&-
), de sorte que le gestionnaire de fichiers n'est pas accessible. Son entrée standard (fd 0), qui correspond aux points ouverts en lecture / écriture, est redirigée (en réalité, le fd est copié ->&0
) vers sa sortie standard (fd 1).Cela crée un sous-shell avec son entrée et sa sortie standard connectées aux pts. Il peut être envoyé en entrée en écrivant sur le ptm et sa sortie peut être vue en lisant à partir du ptm:
Le sous-shell exécute cette commande:
Il s'exécute
bash(6527,6527)
en-i
mode interactif ( ) dans une nouvelle session (setsid -c
notez que le PID et le PGID sont identiques). Son erreur standard est redirigée vers sa sortie standard (2>&1
) et acheminée via:tee(6528,6524)
il est donc écrit dans unlog
fichier ainsi que dans les pts. Cela donne une autre façon de voir la sortie du sous-shell:Le sous-shell s'exécutant de manière
bash
interactive, il est possible d'envoyer des commandes à exécuter, comme dans cet exemple qui affiche les descripteurs de fichier du sous-shell:La lecture de la sortie du sous-shell (
tail -f log
oucat <&3
) révèle:L'entrée standard (fd 0) est connectée aux pts et la sortie standard (fd 1) et l'erreur (fd 2) sont connectées au même tuyau, celui qui se connecte à
tee
:Et un regard sur les descripteurs de fichier de
tee
La sortie standard (fd 1) correspond aux points: tout ce qui est écrit en “tee” sur sa sortie standard est renvoyé à la ptm. Erreur standard (fd 2) correspond aux points appartenant au terminal de contrôle.
Envelopper
Le script suivant utilise la technique décrite ci-dessus. Il configure une
bash
session interactive qui peut être injectée en écrivant dans un descripteur de fichier. Il est disponible ici et documenté avec des explications.la source
bind '"\e[0n": "ls -l"'; printf '\e[5n'
solution la plus simple , après tout, le résultat de la sortie serals -l
également^[[0n
affiché sur le terminal une fois que j'aurai appuyé sur la touche Entréels -l
. Des idées comment "cacher" s'il vous plaît? Merci.PS1="\r\e[M$PS1"
avant de fairebind '"\e[0n": "ls -l"'; printf '\e[5n'
et cela a donné l'effet que vous décrivez.Cela dépend de ce que vous entendez par
bash
seulement . Si vous voulez parler d'une seulebash
session interactive , la réponse est presque certainement non . Et c’est parce que même lorsque vous entrez une commande commels -l
sur la ligne de commande de n’importe quel terminal canonique, alorsbash
n’en êtes même pas conscient - etbash
n’est même pas impliqué à ce stade.Au lieu de cela, ce qui s’est passé jusqu’à présent, c’est que la discipline de ligne du noyau a été mise en mémoire tampon et que
stty echo
l’utilisateur n’a saisi que l’écran. Il efface cette entrée pour son lecteur -bash
, dans votre cas, - ligne par ligne - et traduit généralement les\r
résumés en\n
lignes électroniques sur les systèmes Unix - et il enbash
est de même - et votre script source ne peut donc en être informé - il est conscient qu'il en existe entrer du tout jusqu'à ce que l'utilisateur appuie sur laENTER
touche.Maintenant, il y a quelques solutions de rechange. Le plus robuste n'est en réalité pas une solution de contournement et implique l'utilisation de plusieurs processus ou programmes spécialement écrits pour séquencer les entrées, masquer la discipline de lignes
-echo
à l'utilisateur et n'écrire que sur l'écran ce qui est jugé approprié pendant l'interprétation des entrées. spécialement lorsque nécessaire. Cela peut être difficile à faire, car cela implique d’écrire des règles d’interprétation capables de gérer une entrée arbitraire, caractère par caractère, et de les écrire simultanément sans erreur afin de simuler les attentes de l’utilisateur moyen dans ce scénario. C’est pour cette raison, probablement, que les entrées / sorties de terminaux interactifs sont si rarement bien comprises - une perspective difficile n’est pas celle qui se prête à une investigation plus poussée pour la plupart.Une autre solution pourrait impliquer l’émulateur de terminal. Vous dites qu'un problème pour vous est une dépendance à X et à
xdotool
. Dans ce cas, une solution de ce genre que je suis sur le point de proposer pourrait poser des problèmes similaires, mais je vais continuer avec la même chose.Cela fonctionne dans un
xterm
w / l'allowwindowOps
ensemble des ressources. Il enregistre d’abord les noms d’icône / de fenêtre sur une pile, puis configure la chaîne d’icônes du terminal pour demander^Umy command
ensuite au terminal d’injecter ce nom dans la file d’entrée, puis le réinitialise aux valeurs enregistrées. Cela devrait fonctionner de manière invisible pour lesbash
shells interactifs exécutés dans unexterm
configuration correcte, mais c'est probablement une mauvaise idée. Veuillez voir les commentaires de Stéphane ci-dessous.Voici cependant une photo de mon terminal Terminology que j'ai prise après avoir exécuté le
printf
bit avec une séquence d'échappement différente sur ma machine. Pour chaque nouvelle ligne de laprintf
commande, j'ai tapéCTRL+V
puisCTRL+J
, puis appuyé sur laENTER
touche. Je n'ai rien saisi après, mais, comme vous pouvez le constater, le terminal s'est injectémy command
dans la file d'attente de saisie de la discipline de ligne:La vraie façon de faire est d'utiliser un pty imbriqué. C’est comment
screen
ettmux
un travail similaire - les deux, à propos, peuvent vous rendre cela possible.xterm
vient en fait avec un petit programme appeléluit
qui peut également rendre cela possible. Ce n'est pas facile, cependant.Voici une façon dont vous pourriez:
Ce n'est en aucun cas portable, mais devrait fonctionner sur la plupart des systèmes Linux avec les autorisations appropriées pour l'ouverture
/dev/ptmx
. Mon utilisateur est dans letty
groupe, ce qui est suffisant sur mon système. Vous aurez aussi besoin de ...... qui, lorsqu'il est exécuté sur un système GNU (ou tout autre avec un compilateur C standard pouvant également lire stdin) , écrira un petit binaire exécutable nommé
pts
qui exécutera launlockpt()
fonction sur son stdin et écrira sur son stdout nom du périphérique pty qu'il vient de déverrouiller. Je l'ai écrit en travaillant sur ... Comment puis-je venir par ce pty et que puis-je faire avec? .Quoi qu'il en soit, le bit de code ci-dessus exécute un
bash
shell dans un pty un calque situé sous le tty actuel.bash
est dit d'écrire toutes les sorties sur le pty esclave, et le tty actuel est configuré non pas pour-echo
son entrée ni pour le mettre en tampon, mais plutôt pour le transmettre (principalement)raw
àcat
, qui le copiebash
. Et pendant ce temps-là, l’arrière-plancat
copie toutes les sorties d’esclaves vers le tty actuel.Pour la plupart, la configuration ci-dessus serait totalement inutile - simplement redondante, en gros - sauf que nous lançons
bash
avec une copie de son propre fichier pty master fd on<>9
. Cela signifie que vousbash
pouvez écrire librement dans son propre flux d’entrée avec une simple redirection. Tout ce qu'ilbash
faut faire c'est:... se parler.
Voici une autre image:
la source
xterm
, vous pouvez toujours interroger le titre de l'icône avec,\e[20t
mais seulement s'il est configuré avecallowWindowOps: true
.xterm
w / the approprié config. W / an xterm proprement dit, vous pouvez lire et écrire le tampon copier / coller et cela devient donc plus simple, je pense. Xterm a aussi des séquences d'échappement pour changer / affecter la description du terme lui-même.\e[20t
(pas\e]1;?\a
)?
requête concerne uniquement la police de caractères et la couleur , pas les titresBien que la
ioctl(,TIOCSTI,)
réponse de Stéphane Chazelas soit, bien sûr, la bonne réponse, certaines personnes pourraient être assez satisfaites de cette réponse partielle mais triviale: il suffit de placer la commande dans la pile d’historique, l’utilisateur peut alors déplacer une ligne vers le haut pour rechercher le commander.Cela peut devenir un script simple, qui a son propre historique en 1 ligne:
read -e
active l'édition en lecture de l'entrée,-p
est une invite.la source
. foo.sh
ou `source foo.sh, au lieu d'être exécuté dans un sous-shell.) Une approche intéressante, cependant. Un hack similaire qui nécessite de modifier le contexte du shell appelant serait de définir un achèvement personnalisé qui étendrait la ligne vide à quelque chose, puis restaurerait l'ancien gestionnaire d'achèvement.eval
si vous avez des commandes simples à éditer, sans tuyaux ni redirection, etc.Oh, ma parole, nous avons manqué une solution simple intégrée à bash : la
read
commande a une option-i ...
qui, lorsqu'elle est utilisée avec-e
, insère du texte dans le tampon de saisie. De la page de manuel:Créez donc une petite fonction bash ou un script shell qui prend la commande à présenter à l'utilisateur et exécute ou évalue sa réponse:
Ceci utilise sans doute l'ioctl (, TIOCSTI), utilisé depuis plus de 32 ans, comme il existait déjà dans 2.9BSD ioctl.h .
la source