linux - pipes - ¿Cómo puedo canalizar la entrada inicial al proceso que luego será interactiva?
pipes linux c (4)
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)
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
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, genera un programa conectado a este pseudoterminal. Primero alimenta la entrada extra pasada a través de la línea de comandos, y luego lo alimenta a la entrada dada por el usuario a través de stdin.
Por ejemplo, ptypipe "import this" python3
hace que Python ejecute "import this" primero, y luego lo transfiere al prompt interactivo, con finalización de trabajo y otras cosas.
Del mismo modo, ptypipe "date" bash
ejecuta Bash, que ejecuta la date
y luego le da un shell. 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)
No necesita escribir una nueva herramienta para reenviar stdin
; ya se ha escrito una ( cat
):
(echo "initial command" && cat) | some_tool
Esto tiene la desventaja de conectar una tubería a some_tool
, no a una terminal.
Tal vez podrías usar un documento aquí para pasar tu entrada a abd
. P.ej. así (usando bc
para hacer un cálculo simple como ejemplo).
[axe@gromp ~]$ bc <<END
> 3 + 4
> END
7
La sesión bc
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.