¿Es posible en un shell bash interactivo ingresar un comando que muestre algo de texto para que aparezca en el siguiente símbolo del sistema, como si el usuario hubiera ingresado ese texto en ese indicador?
Quiero poder crear sourceuna secuencia de comandos que genere una línea de comandos y la genere para que aparezca cuando el mensaje regrese después de que finalice la secuencia de comandos para que el usuario pueda editarla opcionalmente antes de presionar enterpara ejecutarla.
Esto se puede lograr con, xdotoolpero eso solo funciona cuando el terminal está en una ventana X y solo si está instalado.
[me@mybox] 100 $ xdotool type "ls -l"
[me@mybox] 101 $ ls -l <--- cursor appears here!
¿Se puede hacer esto solo con bash?

Respuestas:
Con
zsh, puede usarprint -zpara colocar algo de texto en el búfer del editor de línea para el siguiente mensaje:imprimirá el editor de líneas con el
echo testque puede editar en el siguiente indicador.No creo que
bashtenga una característica similar, sin embargo, en muchos sistemas, puede cebar el búfer de entrada del dispositivo terminal conTIOCSTIioctl():Se insertaría
echo testen el búfer de entrada del dispositivo terminal, como si se recibiera del terminal.Una variación más portátil en el
Terminologyenfoque de @ mike y que no sacrifica la seguridad sería enviar al emulador de terminal unaquery status reportsecuencia de escape bastante estándar :<ESC>[5nqué terminales responden invariablemente (como entrada) como<ESC>[0ny lo vinculan a la cadena que desea insertar:Si dentro de GNU
screen, también puedes hacer:Ahora, excepto por el enfoque TIOCSTI ioctl, le estamos pidiendo al emulador de terminal que nos envíe alguna cadena como si estuviera escrita. Si esa cadena viene antes
readline(basheditor de línea 's) se ha desactivado el eco local terminal, luego de que la cadena se mostrará no en el intérprete de comandos, arruinando la pantalla ligeramente.Para evitarlo, puede retrasar un poco el envío de la solicitud al terminal para asegurarse de que la respuesta llegue cuando readline haya deshabilitado el eco.
(aquí asumiendo que su
sleepresolución es compatible por debajo del segundo).Lo ideal sería hacer algo como:
Sin embargo
bash(al contrario dezsh) no tiene soporte para talwait-until-the-response-arrivesque no lea la respuesta.Sin embargo, tiene una
has-the-response-arrived-yetfunción conread -t0:Otras lecturas
Vea las respuestas de @ starfry que amplían las dos soluciones dadas por @mikeserv y por mí con información más detallada.
fuente
bind '"\e[0n": "echo test"'; printf '\e[5n'probablemente la respuesta de bash-only que estoy buscando. Esto funciona para mi. Sin embargo, también me^[[0nimprimen antes de mi solicitud. Descubrí que esto es causado cuando$PS1contiene una subshell. Puede reproducirlo haciendoPS1='$(:)'antes del comando de enlace. ¿Por qué sucedería eso y se puede hacer algo al respecto?\return a la cabeza de$PS1? Eso debería funcionar si$PS1es lo suficientemente largo. Si no, entonces ponlo^[[Mallí.rhace el truco. Por supuesto, esto no impide la salida, solo se sobrescribe antes de que el ojo lo vea. Supongo que^[[Mborra la línea para borrar el texto inyectado en caso de que sea más largo que el mensaje. ¿Es correcto (no pude encontrarlo en la lista de escape ANSI que tengo)?Esta respuesta se proporciona como una aclaración de mi propia comprensión y está inspirada en @ StéphaneChazelas y @mikeserv antes que yo.
TL; DR
bashsin ayuda externa;ioctlperobashsolución más fácil de usarbind.La solución fácil
Bash tiene un shell incorporado llamado
bindque permite ejecutar un comando de shell cuando se recibe una secuencia de teclas. En esencia, la salida del comando de shell se escribe en el búfer de entrada del shell.La secuencia de teclas
\e[0n(<ESC>[0n) es un código de escape de Terminal ANSI que un terminal envía para indicar que está funcionando normalmente. Envía esto en respuesta a una solicitud de informe de estado del dispositivo que se envía como<ESC>[5n.Al vincular la respuesta a un
echomensaje que genera el texto a inyectar, podemos inyectar ese texto siempre que lo solicitemos solicitando el estado del dispositivo y eso se hace enviando una<ESC>[5nsecuencia de escape.Esto funciona y probablemente sea suficiente para responder la pregunta original porque no hay otras herramientas involucradas. Es puro
bashpero se basa en un terminal que se comporta bien (prácticamente todos lo son).Deja el texto repetido en la línea de comando listo para ser utilizado como si hubiera sido escrito. Se puede agregar, editar y presionar
ENTERhace que se ejecute.Agregue
\nal comando enlazado para que se ejecute automáticamente.Sin embargo, esta solución solo funciona en el terminal actual (que está dentro del alcance de la pregunta original). Funciona desde una solicitud interactiva o desde un script de origen, pero genera un error si se usa desde una subshell:
La solución correcta que se describe a continuación es más flexible, pero se basa en comandos externos.
La solución correcta
La forma correcta de inyectar entrada usa tty_ioctl , una llamada al sistema de Unix para el control de E / S que tiene un
TIOCSTIcomando que se puede usar para inyectar entrada.TIOC de " T erminal IOC tl " y STI de " S end T erminal I nput ".
No hay un comando incorporado
bashpara esto; hacerlo requiere un comando externo. No existe dicho comando en la distribución típica de GNU / Linux, pero no es difícil de lograr con un poco de programación. Aquí hay una función de shell que usaperl:Aquí
0x5412está el código para elTIOCSTIcomando.TIOCSTIes una constante definida en los archivos de cabecera C estándar con el valor0x5412. Probargrep -r TIOCSTI /usr/includeo mirar hacia adentro/usr/include/asm-generic/ioctls.h; está incluido en los programas C indirectamente por#include <sys/ioctl.h>.Entonces puedes hacer:
Las implementaciones en algunos otros idiomas se muestran a continuación (guarde en un archivo y luego
chmod +x):Perl
inject.plPuede generar lo
sys/ioctl.phque define enTIOCSTIlugar de usar el valor numérico. Ver aquíPitón
inject.pyRubí
inject.rbdo
inject.ccompilar con
gcc -o inject inject.c**! ** Hay más ejemplos aquí .
Usar
ioctlpara hacer esto funciona en subcapas. También puede inyectarse en otros terminales como se explica a continuación.Llevándolo más lejos (controlando otros terminales)
Está más allá del alcance de la pregunta original, pero es posible inyectar caracteres en otro terminal, sujeto a tener los permisos apropiados. Normalmente esto significa ser
root, pero vea a continuación otras formas.Extender el programa C dado anteriormente para aceptar un argumento de línea de comandos que especifique tty de otro terminal permite inyectar en ese terminal:
También envía una nueva línea de forma predeterminada, pero, de forma similar
echo, proporciona una-nopción para suprimirla. La opción--to--ttyrequiere un argumento: elttydel terminal a inyectar. El valor para esto se puede obtener en ese terminal:Compilarlo con
gcc -o inject inject.c. Prefije el texto para inyectar--si contiene guiones para evitar que el analizador de argumentos malinterprete las opciones de la línea de comandos. Ver./inject --help. Úselo así:o solo
para inyectar el terminal actual.
Inyectar en otro terminal requiere derechos administrativos que se pueden obtener mediante:
root,sudo,CAP_SYS_ADMINcapacidad osetuidPara asignar
CAP_SYS_ADMIN:Para asignar
setuid:Salida limpia
El texto inyectado aparece antes de la solicitud como si se hubiera escrito antes de que apareciera la solicitud (que, en efecto, lo fue) pero luego vuelve a aparecer después de la solicitud.
Una forma de ocultar el texto que aparece antes de la solicitud es anteponer la solicitud con un retorno de carro (
\rno salto de línea) y borrar la línea actual (<ESC>[M):Sin embargo, esto solo borrará la línea en la que aparece el mensaje. Si el texto inyectado incluye nuevas líneas, entonces esto no funcionará según lo previsto.
Otra solución deshabilita el eco de los caracteres inyectados. Un contenedor utiliza
sttypara hacer esto:donde
injectes una de las soluciones descritas anteriormente, o reemplazada porprintf '\e[5n'.Aproximaciones alternativas
Si su entorno cumple con ciertos requisitos previos, entonces puede tener otros métodos disponibles que puede usar para inyectar entradas. Si está en un entorno de escritorio, entonces xdotool es una utilidad X.Org que simula la actividad del mouse y el teclado, pero es posible que su distribución no lo incluya de manera predeterminada. Puedes probar:
Si usa tmux , el multiplexor terminal, puede hacer esto:
donde
-tselecciona qué sesión y panel inyectar. GNU Screen tiene una capacidad similar con sustuffcomando:Si su distribución incluye el paquete de herramientas de la consola, entonces puede tener un
writevtcomando que se usaioctlcomo nuestros ejemplos. Sin embargo, la mayoría de las distribuciones han desaprobado este paquete a favor de kbd que carece de esta característica.Se puede compilar una copia actualizada de writevt.c usando
gcc -o writevt writevt.c.Otras opciones que pueden adaptarse mejor a algunos casos de uso incluyen esperar y vaciar, que están diseñadas para permitir que las herramientas interactivas sean programadas.
También podría usar un shell que admita la inyección de terminal, como lo
zshque puede hacerprint -z ls.La respuesta "Wow, eso es inteligente ..."
El método descrito aquí también se discute aquí y se basa en el método discutido aquí .
Un redireccionamiento de shell
/dev/ptmxobtiene un nuevo pseudo-terminal:Una pequeña herramienta escrita en C que desbloquea el pseudoterminal master (ptm) y muestra el nombre del pseudoterminal slave (pts) en su salida estándar.
(guardar como
pts.cy compilar congcc -o pts pts.c)Cuando se llama al programa con su entrada estándar establecida en un ptm, desbloquea los pts correspondientes y envía su nombre a la salida estándar.
La función unlockpt () desbloquea el dispositivo pseudoterminal esclavo correspondiente al pseudoterminal maestro al que se refiere el descriptor de archivo dado. El programa pasa esto a cero, que es la entrada estándar del programa .
La función ptsname () devuelve el nombre del dispositivo pseudoterminal esclavo correspondiente al maestro al que hace referencia el descriptor de archivo dado, pasando nuevamente cero para la entrada estándar del programa.
Se puede conectar un proceso a los pts. Primero obtenga un ptm (aquí se asigna al descriptor de archivo 3, abierto lectura-escritura por la
<>redirección).Luego comienza el proceso:
Los procesos generados por esta línea de comandos se ilustran mejor con
pstree:El resultado es relativo al shell actual (
$$) y el PID (-p) y el PGID (-g) de cada proceso se muestran entre paréntesis(PID,PGID).En la parte superior del árbol se encuentra
bash(5203,5203)el shell interactivo en el que estamos escribiendo comandos, y sus descriptores de archivos lo conectan a la aplicación de terminal que estamos usando para interactuar con él (xtermo similar).Al mirar el comando nuevamente, el primer conjunto de paréntesis comenzó una subshell,
bash(6524,6524)con su descriptor de archivo 0 (su entrada estándar ) asignado a los pts (que se abre lectura-escritura<>) , como lo devuelve otra subshell que se ejecutó./pts <&3para desbloquear el pts asociados con el descriptor de archivo 3 (creado en el paso anteriorexec 3<>/dev/ptmx).El descriptor de archivo de la subshell 3 está cerrado (
3>&-) para que el ptm no sea accesible para él. Su entrada estándar (fd 0), que es el pts que se abrió lectura / escritura, se redirige (en realidad, se copia la fd>&0) a su salida estándar (fd 1).Esto crea una subshell con su entrada y salida estándar conectadas a los pts. Se puede enviar entrada escribiendo al ptm y su salida se puede ver leyendo desde el ptm:
La subshell ejecuta este comando:
Se ejecuta
bash(6527,6527)en modo interactivo (-i) en una nueva sesión (setsid -ctenga en cuenta que el PID y el PGID son los mismos). Su error estándar se redirige a su salida estándar (2>&1) y se canalizatee(6528,6524)para que se escriba en unlogarchivo y en los pts. Esto le da otra forma de ver la salida del subshell:Debido a que la subshell se ejecuta de forma
bashinteractiva, se pueden enviar comandos para ejecutar, como este ejemplo que muestra los descriptores de archivo de la subshell:La lectura de la salida del subshell (
tail -f logocat <&3) revela:La entrada estándar (fd 0) está conectada a los pts y tanto la salida estándar (fd 1) como el error (fd 2) están conectados a la misma tubería, la que se conecta a
tee:Y un vistazo a los descriptores de archivo de
teeLa salida estándar (fd 1) es el pts: todo lo que 'tee' escribe en su salida estándar se envía de vuelta al ptm. Error estándar (fd 2) son los pts que pertenecen al terminal de control.
Envolviendolo
El siguiente script utiliza la técnica descrita anteriormente. Configura una
bashsesión interactiva que puede inyectarse escribiendo en un descriptor de archivo. Está disponible aquí y está documentado con explicaciones.fuente
bind '"\e[0n": "ls -l"'; printf '\e[5n'solución más fácil , después de todo, la salida dells -ltambién se^[[0nemitirá en la Terminal una vez que presione la tecla Intro, así se ejecutaráls -l. ¿Alguna idea de cómo "ocultarlo" por favor? Gracias.PS1="\r\e[M$PS1"antes de hacerlobind '"\e[0n": "ls -l"'; printf '\e[5n'y eso dio el efecto que usted describe.Depende de lo que quieras decir con
bashsolo . Si te refieres a una únicabashsesión interactiva , entonces la respuesta es casi definitivamente no . Y esto se debe a que incluso cuando ingresa un comando comols -len la línea de comando en cualquier terminal canónica,bashni siquiera lo sabe, ybashni siquiera está involucrado en ese punto.Más bien, lo que ha sucedido hasta ese momento es que la disciplina de línea tty del núcleo se ha amortiguado y
stty echola entrada del usuario solo se ha introducido en la pantalla. Enjuaga esa entrada a su lectorbash, en su caso de ejemplo, línea por línea, y generalmente traduce\returns a\nlíneas electrónicas en sistemas Unix también, y asíbashno es, y tampoco se puede hacer que su script de origen se dé cuenta de que hay entrada hasta que el usuario presione laENTERtecla.Ahora, hay algunas soluciones alternativas. El más robusto no es una solución en absoluto, en realidad, e implica el uso de múltiples procesos o programas especialmente escritos para secuenciar la entrada, ocultar la disciplina de línea
-echodel usuario y solo escribir en la pantalla lo que se considera apropiado al interpretar la entrada. especialmente cuando sea necesario. Esto puede ser difícil de hacer bien porque significa escribir reglas de interpretación que pueden manejar la entrada arbitraria char por char a medida que llega y escribirla simultáneamente sin error para simular lo que el usuario promedio esperaría en ese escenario. Es por esta razón, probablemente, que la E / S del terminal interactivo rara vez se entiende bien, una perspectiva que difícil no es la que se presta a una mayor investigación para la mayoría.Otra solución podría involucrar el emulador de terminal. Dices que un problema para ti es una dependencia de X y de
xdotool. En ese caso, tal solución que voy a ofrecer podría tener problemas similares, pero seguiré adelante de todos modos.Eso funcionará en un
xtermw / theallowwindowOpsconjunto de recursos. Primero guarda los nombres de los íconos / ventanas en una pila, luego establece la cadena de íconos del terminal para^Umy commandluego solicitar que el terminal inyecte ese nombre en la cola de entrada y finalmente lo restablece a los valores guardados. Debería funcionar de forma invisible para losbashshells interactivos que se ejecutanxtermcon la configuración correcta, pero probablemente sea una mala idea. Por favor, vea los comentarios de Stéphane a continuación.Sin embargo, aquí hay una foto que tomé de mi terminal de Terminología después de ejecutar el
printfbit con una secuencia de escape diferente en mi máquina. Para cada nueva línea en elprintforden que he escritoCTRL+Va continuación,CTRL+Jy después de presionar elENTERtecla. No escribí nada después, pero, como puede ver, el terminal inyectadomy commanden la cola de entrada de la disciplina de línea para mí:La verdadera forma de hacerlo es con una pty anidada. Es como
screenytmuxun trabajo similar, los cuales, por cierto, pueden hacer esto posible para usted.xtermen realidad viene con un pequeño programa llamadoluitque también puede hacer esto posible. Sin embargo, no es fácil.Aquí hay una forma en que podría:
De ninguna manera es portátil, pero debería funcionar en la mayoría de los sistemas Linux con los permisos adecuados para abrir
/dev/ptmx. Mi usuario esta enttygrupo que es suficiente en mi sistema. También necesitarás ...... que, cuando se ejecuta en un sistema GNU (o cualquier otro con un compilador C estándar que también puede leer desde stdin) , escribirá un pequeño binario ejecutable llamado
ptsque ejecutará launlockpt()función en su stdin y escribirá en su stdout el nombre del dispositivo pty que acaba de desbloquear. Lo escribí cuando trabajaba en ... ¿Cómo llego a este pie y qué puedo hacer con él? .De todos modos, lo que hace el bit de código anterior es ejecutar un
bashshell en una capa debajo de la tty actual.bashse le dice que escriba toda la salida en el pty esclavo, y el tty actual no está configurado en-echosu entrada ni en el búfer, sino en su lugar para pasarlo (principalmente)rawacat, a la que la copiabash. Y mientras tanto, otracatcopia en segundo plano copia toda la salida esclava al tty actual.En su mayor parte, la configuración anterior sería completamente inútil, básicamente redundante, excepto que la ejecutamos
bashcon una copia de su propio pty master fd on<>9. Esto significa quebashpuede escribir libremente en su propia secuencia de entrada con una simple redirección. Todo lo quebashtiene que hacer es:... para hablar consigo mismo.
Aquí hay otra foto:
fuente
xterm, aún puede consultar el título del icono con,\e[20tpero solo si está configurado conallowWindowOps: true.xtermw / el apropiado config. Sin embargo, con un xterm adecuado, puedes leer y escribir el búfer de copiar / pegar y, por lo tanto, se vuelve más simple, creo. Xterm también tiene secuencias de escape para cambiar / afectar la descripción del término en sí.\e[20t(no\e]1;?\a)?consulta es solo para la fuente y el color allí , no para los títulosAunque la
ioctl(,TIOCSTI,)respuesta de Stéphane Chazelas es, por supuesto, la respuesta correcta, algunas personas podrían estar lo suficientemente contentas con esta respuesta parcial pero trivial: simplemente presione el comando en la pila de historial, luego el usuario puede mover 1 línea en el historial para encontrar el mando.Esto puede convertirse en un script simple, que tiene su propio historial de 1 línea:
read -epermite la edición de línea de lectura de la entrada,-pes un aviso.fuente
. foo.sho `source foo.sh, en lugar de ejecutarse en una subshell). Sin embargo, es un enfoque interesante. Un truco similar que requiere modificar el contexto del shell de llamada sería configurar una finalización personalizada que expandiera la línea vacía a algo, y luego restaurara el antiguo controlador de finalización.evalsi tiene comandos simples para editar, sin tuberías y redirección, etc.Oh, mi palabra, perdimos una solución simple incorporada a bash : el
readcomando tiene una opción-i ..., que cuando se usa con-e, empuja el texto al búfer de entrada. Desde la página del manual:Por lo tanto, cree una pequeña función bash o script de shell que tome el comando para presentar al usuario y ejecute o evalúe su respuesta:
Esto sin duda utiliza el ioctl (, TIOCSTI,) que ha existido durante más de 32 años, ya que ya existía en 2.9BSD ioctl.h .
fuente