¿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 source
una 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 enter
para ejecutarla.
Esto se puede lograr con, xdotool
pero 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 -z
para 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 test
que puede editar en el siguiente indicador.No creo que
bash
tenga una característica similar, sin embargo, en muchos sistemas, puede cebar el búfer de entrada del dispositivo terminal conTIOCSTI
ioctl()
:Se insertaría
echo test
en el búfer de entrada del dispositivo terminal, como si se recibiera del terminal.Una variación más portátil en el
Terminology
enfoque de @ mike y que no sacrifica la seguridad sería enviar al emulador de terminal unaquery status report
secuencia de escape bastante estándar :<ESC>[5n
qué terminales responden invariablemente (como entrada) como<ESC>[0n
y 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
(bash
editor 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
sleep
resolució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-arrives
que no lea la respuesta.Sin embargo, tiene una
has-the-response-arrived-yet
funció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^[[0n
imprimen antes de mi solicitud. Descubrí que esto es causado cuando$PS1
contiene una subshell. Puede reproducirlo haciendoPS1='$(:)'
antes del comando de enlace. ¿Por qué sucedería eso y se puede hacer algo al respecto?\r
eturn a la cabeza de$PS1
? Eso debería funcionar si$PS1
es lo suficientemente largo. Si no, entonces ponlo^[[M
allí.r
hace el truco. Por supuesto, esto no impide la salida, solo se sobrescribe antes de que el ojo lo vea. Supongo que^[[M
borra 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
bash
sin ayuda externa;ioctl
perobash
solución más fácil de usarbind
.La solución fácil
Bash tiene un shell incorporado llamado
bind
que 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
echo
mensaje 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>[5n
secuencia de escape.Esto funciona y probablemente sea suficiente para responder la pregunta original porque no hay otras herramientas involucradas. Es puro
bash
pero 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
ENTER
hace que se ejecute.Agregue
\n
al 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
TIOCSTI
comando 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
bash
para 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í
0x5412
está el código para elTIOCSTI
comando.TIOCSTI
es una constante definida en los archivos de cabecera C estándar con el valor0x5412
. Probargrep -r TIOCSTI /usr/include
o 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.pl
Puede generar lo
sys/ioctl.ph
que define enTIOCSTI
lugar de usar el valor numérico. Ver aquíPitón
inject.py
Rubí
inject.rb
do
inject.c
compilar con
gcc -o inject inject.c
**! ** Hay más ejemplos aquí .
Usar
ioctl
para 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-n
opción para suprimirla. La opción--t
o--tty
requiere un argumento: eltty
del 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_ADMIN
capacidad osetuid
Para 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 (
\r
no 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
stty
para hacer esto:donde
inject
es 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
-t
selecciona qué sesión y panel inyectar. GNU Screen tiene una capacidad similar con sustuff
comando:Si su distribución incluye el paquete de herramientas de la consola, entonces puede tener un
writevt
comando que se usaioctl
como 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
zsh
que 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/ptmx
obtiene 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.c
y 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 (xterm
o 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 <&3
para 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 -c
tenga 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 unlog
archivo y en los pts. Esto le da otra forma de ver la salida del subshell:Debido a que la subshell se ejecuta de forma
bash
interactiva, 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 log
ocat <&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
tee
La 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
bash
sesió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 -l
también se^[[0n
emitirá 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
bash
solo . Si te refieres a una únicabash
sesión interactiva , entonces la respuesta es casi definitivamente no . Y esto se debe a que incluso cuando ingresa un comando comols -l
en la línea de comando en cualquier terminal canónica,bash
ni siquiera lo sabe, ybash
ni 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 echo
la 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\r
eturns a\n
líneas electrónicas en sistemas Unix también, y asíbash
no es, y tampoco se puede hacer que su script de origen se dé cuenta de que hay entrada hasta que el usuario presione laENTER
tecla.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
-echo
del 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
xterm
w / theallowwindowOps
conjunto de recursos. Primero guarda los nombres de los íconos / ventanas en una pila, luego establece la cadena de íconos del terminal para^Umy command
luego 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 losbash
shells interactivos que se ejecutanxterm
con 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
printf
bit con una secuencia de escape diferente en mi máquina. Para cada nueva línea en elprintf
orden que he escritoCTRL+V
a continuación,CTRL+J
y después de presionar elENTER
tecla. No escribí nada después, pero, como puede ver, el terminal inyectadomy command
en la cola de entrada de la disciplina de línea para mí:La verdadera forma de hacerlo es con una pty anidada. Es como
screen
ytmux
un trabajo similar, los cuales, por cierto, pueden hacer esto posible para usted.xterm
en realidad viene con un pequeño programa llamadoluit
que 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 entty
grupo 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
pts
que 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
bash
shell en una capa debajo de la tty actual.bash
se le dice que escriba toda la salida en el pty esclavo, y el tty actual no está configurado en-echo
su entrada ni en el búfer, sino en su lugar para pasarlo (principalmente)raw
acat
, a la que la copiabash
. Y mientras tanto, otracat
copia 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
bash
con una copia de su propio pty master fd on<>9
. Esto significa quebash
puede escribir libremente en su propia secuencia de entrada con una simple redirección. Todo lo quebash
tiene que hacer es:... para hablar consigo mismo.
Aquí hay otra foto:
fuente
xterm
, aún puede consultar el título del icono con,\e[20t
pero solo si está configurado conallowWindowOps: true
.xterm
w / 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 -e
permite la edición de línea de lectura de la entrada,-p
es un aviso.fuente
. foo.sh
o `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.eval
si tiene comandos simples para editar, sin tuberías y redirección, etc.Oh, mi palabra, perdimos una solución simple incorporada a bash : el
read
comando 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