Pregunta ¿Cómo puedo canalizar la entrada inicial al proceso que luego será interactiva?


Me gustaría poder insertar un comando inicial en el lanzamiento de un proceso interactivo, para que pueda hacer algo como esto:

echo "initial command" | INSERT_MAGIC_HERE some_tool

tool> initial command 

[result of initial command] 

tool> [now I type an interactive command]

Lo que no funciona:

  • Solo conectar el comando inicial no funciona, ya que esto no se conecta a la terminal.

  • Escribir en / dev / pts / [number] envía la salida al terminal, no ingresa al proceso como si fuera del terminal

Lo que haría, pero con desventajas:

  • Haga un comando que bifurca a un niño, escribe en su stdin y luego reenvía todo desde su propio stdin. Desventaja: las cosas de control de la terminal (como el modo de línea frente a carácter) no funcionarán. ¿Tal vez podría hacer algo con el uso de pseudo terminales?

  • Cree una versión modificada de xterm (de todas formas, voy a ejecutar una para esta tarea) con una opción de línea de comando para inyectar comandos adicionales después de encontrar una cadena de solicitud deseada. Feo.

  • Haga una versión modificada de la herramienta que estoy tratando de ejecutar para que acepte un comando inicial en la línea de comando. Rompe la instalación estándar.

(La herramienta de interés actual, por cierto, es el shell adb de Android: quiero abrir un shell interactivo en el teléfono, ejecutar un comando automáticamente y luego tener una sesión interactiva)


32
2018-04-30 18:18


origen


Respuestas:


No necesita escribir una nueva herramienta para reenviar stdin - uno ya ha sido escrito (cat)

(echo "initial command" && cat) | some_tool

Esto tiene la desventaja de conectar una tubería a some_toolno es una terminal


33
2018-05-02 00:55



La respuesta aceptada es simple y generalmente buena.

Pero tiene una desventaja: los programas obtienen una tubería como entrada, no una terminal. Esto significa que el autocompletado no funcionará. En muchos casos, esto también desactiva la salida bonita, y he escuchado que algunos programas simplemente se niegan a trabajar si stdin no es una terminal.

El siguiente programa resuelve el problema. Crea un pseudoterminal, Engendra un programa conectado a este pseudoterminal. Primero se alimenta entrada adicional pasada a través de la línea de comandos, y luego la alimenta entrada dada por usuario a través de stdin.

Por ejemplo, ptypipe "import this" python3 hace que Python ejecute "importar esto" primero, y luego lo deja en el indicador de comando interactivo, con terminación de trabajo y otras cosas.

Igualmente, ptypipe "date" bash ejecuta Bash, que ejecuta date y luego te da un caparazón. Una vez más, con la finalización de trabajo, el prompt colorizado, etc.

#!/usr/bin/env python3

import sys
import os
import pty
import tty
import select
import subprocess

STDIN_FILENO = 0
STDOUT_FILENO = 1
STDERR_FILENO = 2

def _writen(fd, data):
    while data:
        n = os.write(fd, data)
        data = data[n:]

def main_loop(master_fd, extra_input):
    fds = [master_fd, STDIN_FILENO]

    _writen(master_fd, extra_input)

    while True:
        rfds, _, _ = select.select(fds, [], [])
        if master_fd in rfds:
            data = os.read(master_fd, 1024)
            if not data:
                fds.remove(master_fd)
            else:
                os.write(STDOUT_FILENO, data)
        if STDIN_FILENO in rfds:
            data = os.read(STDIN_FILENO, 1024)
            if not data:
                fds.remove(STDIN_FILENO)
            else:
                _writen(master_fd, data)

def main():
    extra_input = sys.argv[1]
    interactive_command = sys.argv[2]

    if hasattr(os, "fsencode"):
        # convert them back to bytes
        # http://bugs.python.org/issue8776
        interactive_command = os.fsencode(interactive_command)
        extra_input = os.fsencode(extra_input)

    # add implicit newline
    if extra_input and extra_input[-1] != b'\n':
        extra_input += b'\n'

    # replace LF with CR (shells like CR for some reason)
    extra_input = extra_input.replace(b'\n', b'\r')

    pid, master_fd = pty.fork()

    if pid == 0:
        os.execlp("sh", "/bin/sh", "-c", interactive_command)

    try:
        mode = tty.tcgetattr(STDIN_FILENO)
        tty.setraw(STDIN_FILENO)
        restore = True
    except tty.error:    # This is the same as termios.error
        restore = False

    try:
        main_loop(master_fd, extra_input)
    except OSError:
        if restore:
            tty.tcsetattr(0, tty.TCSAFLUSH, mode)

    os.close(master_fd)
    return os.waitpid(pid, 0)[1]

if __name__ == "__main__":
    main()

(Nota: me temo que esta solución contiene un posible punto muerto. Es posible que desee enviar extra_input en trozos pequeños para evitarlo)


3
2018-06-03 23:14



Esto es fácil de hacer con el programa "esperar" que tiene la intención de permitirle escribir scripts para interactuar con los programas.

Probé esto escribiendo un script de espera bc.exp para iniciar la calculadora "bc" y le envié el comando "obase = 16" para ponerlo en modo de salida hexadecimal, y luego darme el control.

La secuencia de comandos (en un archivo llamado bc.exp) es

spawn bc
send "obase=16\n"
interact {
 \003 exit
}

Uno lo ejecuta con

expect bc.exp

1
2017-07-21 02:48



Tal vez podrías usar un documento aquí pasar tu entrada a abd. P.ej. así (usando bc hacer un cálculo simple como ejemplo).

[axe@gromp ~]$ bc <<END
> 3 + 4
> END
7

los bc la sesión permanece abierta después, de modo que lo que se proporciona entre los marcadores de inicio y fin (entre "<< END" y "END") se pasará al comando.


-2
2018-04-30 18:44