¿Puede bash escribir en su propia secuencia de entrada?

39

¿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?

starfry
fuente
Estoy pensando que esto no debería ser difícil con Expect, si puedes tolerar eso y hacer que conduzca una subshell; pero no recuerdo lo suficiente como para publicar una respuesta real.
tripleee

Respuestas:

40

Con zsh, puede usar print -zpara colocar algo de texto en el búfer del editor de línea para el siguiente mensaje:

print -z echo test

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 con TIOCSTI ioctl():

perl -e 'require "sys/ioctl.ph"; ioctl(STDIN, &TIOCSTI, $_)
  for split "", join " ", @ARGV' echo test

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 una query 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:

bind '"\e[0n": "echo test"'; printf '\e[5n'

Si dentro de GNU screen, también puedes hacer:

screen -X stuff 'echo test'

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.

bind '"\e[0n": "echo test"'; ((sleep 0.05;  printf '\e[5n') &)

(aquí asumiendo que su sleepresolución es compatible por debajo del segundo).

Lo ideal sería hacer algo como:

bind '"\e[0n": "echo test"'
stty -echo
printf '\e[5n'
wait-until-the-response-arrives
stty echo

Sin embargo bash(al contrario de zsh) no tiene soporte para tal wait-until-the-response-arrivesque no lea la respuesta.

Sin embargo, tiene una has-the-response-arrived-yetfunción con read -t0:

bind '"\e[0n": "echo test"'
saved_settings=$(stty -g)
stty -echo -icanon min 1 time 0
printf '\e[5n'
until read -t0; do
  sleep 0.02
done
stty "$saved_settings"

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.

Stéphane Chazelas
fuente
Creo que 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 haciendo PS1='$(:)'antes del comando de enlace. ¿Por qué sucedería eso y se puede hacer algo al respecto?
starfry
Aunque todo en esta respuesta es correcto, la pregunta era para bash, no para zsh. A veces no tenemos la opción de elegir qué shell usar.
Falsenames
@Falsenames solo el primer párrafo es para zsh. El resto es agnóstico de concha o específico de bash. Las preguntas y respuestas no tienen que ser útiles solo para los usuarios de bash.
Stéphane Chazelas
1
@starfry parece que tal vez podrías poner una \return a la cabeza de $PS1? Eso debería funcionar si $PS1es lo suficientemente largo. Si no, entonces ponlo ^[[Mallí.
mikeserv
@mikeserv - 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)?
starfry
25

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

  • no es posible hacer esto bashsin ayuda externa;
  • la forma correcta de hacerlo es con una entrada de terminal de envío ioctl pero
  • La bashsolución más fácil de usar bind.

La solución fácil

bind '"\e[0n": "ls -l"'; printf '\e[5n'

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.

$ bind '"\e[0n": "ls -l"'

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.

printf '\e[5n'

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:

bind: warning: line editing not enabled

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 usa perl:

function inject() {
  perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV' "$@"
}

Aquí 0x5412está el código para el TIOCSTIcomando.

TIOCSTIes una constante definida en los archivos de cabecera C estándar con el valor 0x5412. Probar grep -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:

$ inject ls -l
ls -l$ ls -l <- cursor here

Las implementaciones en algunos otros idiomas se muestran a continuación (guarde en un archivo y luego chmod +x):

Perl inject.pl

#!/usr/bin/perl
ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV

Puede generar lo sys/ioctl.phque define en TIOCSTIlugar de usar el valor numérico. Ver aquí

Pitón inject.py

#!/usr/bin/python
import fcntl, sys, termios
del sys.argv[0]
for c in ' '.join(sys.argv):
  fcntl.ioctl(sys.stdin, termios.TIOCSTI, c)

Rubí inject.rb

#!/usr/bin/ruby
ARGV.join(' ').split('').each { |c| $stdin.ioctl(0x5412,c) }

do inject.c

compilar con gcc -o inject inject.c

#include <sys/ioctl.h>
int main(int argc, char *argv[])
{
  int a,c;
  for (a=1, c=0; a< argc; c=0 )
    {
      while (argv[a][c])
        ioctl(0, TIOCSTI, &argv[a][c++]);
      if (++a < argc) ioctl(0, TIOCSTI," ");
    }
  return 0;
}

**! ** 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 serroot , 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:

#include <stdlib.h>
#include <argp.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>

const char *argp_program_version ="inject - see https://unix.stackexchange.com/q/213799";
static char doc[] = "inject - write to terminal input stream";
static struct argp_option options[] = {
  { "tty",  't', "TTY", 0, "target tty (defaults to current)"},
  { "nonl", 'n', 0,     0, "do not output the trailing newline"},
  { 0 }
};

struct arguments
{
  int fd, nl, next;
};

static error_t parse_opt(int key, char *arg, struct argp_state *state) {
    struct arguments *arguments = state->input;
    switch (key)
      {
        case 't': arguments->fd = open(arg, O_WRONLY|O_NONBLOCK);
                  if (arguments->fd > 0)
                    break;
                  else
                    return EINVAL;
        case 'n': arguments->nl = 0; break;
        case ARGP_KEY_ARGS: arguments->next = state->next; return 0;
        default: return ARGP_ERR_UNKNOWN;
      }
    return 0;
}

static struct argp argp = { options, parse_opt, 0, doc };
static struct arguments arguments;

static void inject(char c)
{
  ioctl(arguments.fd, TIOCSTI, &c);
}

int main(int argc, char *argv[])
{
  arguments.fd=0;
  arguments.nl='\n';
  if (argp_parse (&argp, argc, argv, 0, 0, &arguments))
    {
      perror("Error");
      exit(errno);
    }

  int a,c;
  for (a=arguments.next, c=0; a< argc; c=0 )
    {
      while (argv[a][c])
        inject (argv[a][c++]);
      if (++a < argc) inject(' ');
    }
  if (arguments.nl) inject(arguments.nl);

  return 0;
}  

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: el ttydel terminal a inyectar. El valor para esto se puede obtener en ese terminal:

$ tty
/dev/pts/20

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í:

$ inject --tty /dev/pts/22 -- ls -lrt

o solo

$ inject  -- ls -lrt

para inyectar el terminal actual.

Inyectar en otro terminal requiere derechos administrativos que se pueden obtener mediante:

  • emitiendo el comando como root,
  • utilizando sudo,
  • teniendo la CAP_SYS_ADMINcapacidad o
  • configurar el ejecutable setuid

Para asignar CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Para asignar setuid:

$ sudo chown root:root inject
$ sudo chmod u+s inject

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):

$ PS1="\r\e[M$PS1"

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:

saved_settings=$(stty -g)
stty -echo -icanon min 1 time 0
inject echo line one
inject echo line two
until read -t0; do
  sleep 0.02
done
stty "$saved_settings"

donde injectes una de las soluciones descritas anteriormente, o reemplazada por printf '\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:

$ xdotool type ls

Si usa tmux , el multiplexor terminal, puede hacer esto:

$ tmux send-key -t session:pane ls

donde -tselecciona qué sesión y panel inyectar. GNU Screen tiene una capacidad similar con su stuffcomando:

$ screen -S session -p pane -X stuff ls

Si su distribución incluye el paquete de herramientas de la consola, entonces puede tener un writevtcomando que se usa ioctlcomo 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 hacer print -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:

$ $ ls /dev/pts; ls /dev/pts </dev/ptmx
0  1  2  ptmx
0  1  2  3  ptmx

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.

#include <stdio.h>
int main(int argc, char *argv[]) {
    if(unlockpt(0)) return 2;
    char *ptsname(int fd);
    printf("%s\n",ptsname(0));
    return argc - 1;
}

(guardar como pts.cy compilar con gcc -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.

$ ./pts </dev/ptmx
/dev/pts/20
  • 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).

 exec 3<>/dev/ptmx

Luego comienza el proceso:

$ (setsid -c bash -i 2>&1 | tee log) <>"$(./pts <&3)" 3>&- >&0 &

Los procesos generados por esta línea de comandos se ilustran mejor con pstree:

$ pstree -pg -H $(jobs -p %+) $$
bash(5203,5203)─┬─bash(6524,6524)─┬─bash(6527,6527)
                             └─tee(6528,6524)
            └─pstree(6815,6815)

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).

$ ls -l /dev/fd/
lrwx------ 0 -> /dev/pts/3
lrwx------ 1 -> /dev/pts/3
lrwx------ 2 -> /dev/pts/3

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 anterior exec 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:

$ echo 'some input' >&3 # write to subshell
$ cat <&3               # read from subshell

La subshell ejecuta este comando:

setsid -c bash -i 2>&1 | tee log

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 canaliza tee(6528,6524)para que se escriba en un logarchivo y en los pts. Esto le da otra forma de ver la salida del subshell:

$ tail -f log

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:

$ echo 'ls -l /dev/fd/' >&3

La lectura de la salida del subshell ( tail -f logo cat <&3) revela:

lrwx------ 0 -> /dev/pts/17
l-wx------ 1 -> pipe:[116261]
l-wx------ 2 -> pipe:[116261]

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:

$ (find /proc -type l | xargs ls -l | fgrep 'pipe:[116261]') 2>/dev/null
l-wx------ /proc/6527/fd/1 -> pipe:[116261]
l-wx------ /proc/6527/fd/2 -> pipe:[116261]
lr-x------ /proc/6528/fd/0 -> pipe:[116261]

Y un vistazo a los descriptores de archivo de tee

$ ls -l /proc/6528/fd/
lr-x------ 0 -> pipe:[116261]
lrwx------ 1 -> /dev/pts/17
lrwx------ 2 -> /dev/pts/3
l-wx------ 3 -> /home/myuser/work/log

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 bashsesión interactiva que puede inyectarse escribiendo en un descriptor de archivo. Está disponible aquí y está documentado con explicaciones.

sh -cm 'cat <&9 &cat >&9|(             ### copy to/from host/slave
        trap "  stty $(stty -g         ### save/restore stty settings on exit
                stty -echo raw)        ### host: no echo and raw-mode
                kill -1 0" EXIT        ### send a -HUP to host pgrp on EXIT
        <>"$($pts <&9)" >&0 2>&1\
        setsid -wc -- bash) <&1        ### point bash <0,1,2> at slave and setsid bash
' --    9<>/dev/ptmx 2>/dev/null       ### open pty master on <>9
starfry
fuente
Con la bind '"\e[0n": "ls -l"'; printf '\e[5n'solución más fácil , después de todo, la salida del ls -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.
Ali
1
Le presenté una solución que le da el efecto que busca : en la sección de salida limpia de mi respuesta, sugiero agregar un retorno a la solicitud para ocultar el texto superfluo. Lo intenté PS1="\r\e[M$PS1"antes de hacerlo bind '"\e[0n": "ls -l"'; printf '\e[5n'y eso dio el efecto que usted describe.
Starfry
¡Gracias! Perdí totalmente ese punto.
Ali
20

Depende de lo que quieras decir con bashsolo . Si te refieres a una única bashsesión interactiva , entonces la respuesta es casi definitivamente no . Y esto se debe a que incluso cuando ingresa un comando como ls -len la línea de comando en cualquier terminal canónica, bashni siquiera lo sabe, y bashni 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 lector bash, 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 la ENTERtecla.

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.

printf  '\33[22;1t\33]1;%b\33\\\33[20t\33[23;0t' \
        '\025my command'

Eso funcionará en un xtermw / 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 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 los bashshells interactivos que se ejecutan xterm 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 printfbit con una secuencia de escape diferente en mi máquina. Para cada nueva línea en el printforden que he escrito CTRL+Va continuación, CTRL+Jy después de presionar elENTER tecla. No escribí nada después, pero, como puede ver, el terminal inyectado my commanden la cola de entrada de la disciplina de línea para mí:

term_inject

La verdadera forma de hacerlo es con una pty anidada. Es comoscreen y tmuxun trabajo similar, los cuales, por cierto, pueden hacer esto posible para usted. xtermen realidad viene con un pequeño programa llamado luitque también puede hacer esto posible. Sin embargo, no es fácil.

Aquí hay una forma en que podría:

sh -cm 'cat <&9 &cat >&9|(             ### copy to/from host/slave
        trap "  stty $(stty -g         ### save/restore stty settings on exit
                stty -echo raw)        ### host: no echo and raw-mode
                kill -1 0" EXIT        ### send a -HUP to host pgrp on EXIT
        <>"$(pts <&9)" >&0 2>&1\       
        setsid -wc -- bash) <&1        ### point bash <0,1,2> at slave and setsid bash
' --    9<>/dev/ptmx 2>/dev/null       ### open pty master on <>9

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 ...

<<\C cc -xc - -o pts
#include <stdio.h>
int main(int argc, char *argv[]) {
        if(unlockpt(0)) return 2;
        char *ptsname(int fd);
        printf("%s\n",ptsname(0));
        return argc - 1;
}
C

... 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 llamadopts que ejecutará la unlockpt()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) raw a cat, a la que la copia bash. Y mientras tanto, otra catcopia 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 que bashpuede escribir libremente en su propia secuencia de entrada con una simple redirección. Todo lo quebash tiene que hacer es:

echo echo hey >&9

... para hablar consigo mismo.

Aquí hay otra foto:

ingrese la descripción de la imagen aquí

mikeserv
fuente
2
¿En qué terminales lograste que funcionara? Ese tipo de cosas se maltrataban en los viejos tiempos y deberían deshabilitarse por defecto hoy en día. Con xterm, aún puede consultar el título del icono con, \e[20tpero solo si está configurado con allowWindowOps: true.
Stéphane Chazelas
Eso es CVE-2003-0063
Stéphane Chazelas
@ StéphaneChazelas que funciona en Terminología, pero estoy bastante seguro de que también funciona en la terminal gnome, en la terminal kde (olvido su nombre, y creo que hay un escape diferente) , y como dices, w / 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í.
mikeserv
No puedo hacer que eso funcione en otra cosa que en terminología (que por cierto tiene varias otras vulnerabilidades similares). Dado que CVE tiene más de 12 años y es relativamente conocido, me sorprendería si alguno de los principales emuladores de terminal tuviera la misma vulnerabilidad. Tenga en cuenta que con xterm, eso es \e[20t(no \e]1;?\a)
Stéphane Chazelas
1
La ?consulta es solo para la fuente y el color allí , no para los títulos
Stéphane Chazelas
8

Aunque 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.

$ history -s "ls -l"
$ echo "move up 1 line in history to get command to run"

Esto puede convertirse en un script simple, que tiene su propio historial de 1 línea:

#!/bin/bash
history -s "ls -l"
read -e -p "move up 1 line: "
eval "$REPLY"

read -epermite la edición de línea de lectura de la entrada, -pes un aviso.

meuh
fuente
Eso solo funcionará en funciones de shell, o si el script se obtuvo ( . 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.
Peter Cordes
@PeterCordes tienes razón. Estaba tomando la pregunta demasiado literalmente. Pero he agregado un ejemplo de un script simple que podría funcionar.
meuh
@mikeserv Hola, es solo una solución simple que puede ser útil para algunas personas. Incluso puede eliminar el evalsi tiene comandos simples para editar, sin tuberías y redirección, etc.
meuh
1

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:

-i texto

Si se está usando readline para leer la línea, el texto se coloca en el búfer de edición antes de que comience la edición.

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:

domycmd(){ read -e -i "$*"; eval "$REPLY"; }

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 .

meuh
fuente
1
Interesante con un efecto similar, pero no se inyecta a la solicitud sin embargo.
starfry
Pensándolo bien, tienes razón. bash no necesita TIOCSTI ya que está haciendo todo el I / O.
meuh