Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I pipe initial input into process which will then be interactive?

Tags:

linux

unix

pipe

I'd like to be able to inject an initial command into the launching of an interactive process, so that I can do something like this:

echo "initial command" | INSERT_MAGIC_HERE some_tool  tool> initial command   [result of initial command]   tool> [now I type an interactive command] 

What doesn't work:

  • Just piping the initial command in doesn't work, as this results in stdin not being connected to the terminal

  • Writing to /dev/pts/[number] sends the output to the terminal, not input to the process as if it were from the terminal

What would but with disadvantages:

  • Make a command which forks a child, writes to its stdin and then forwards everything from its own stdin. Downside - terminal control things (like line vs character mode) won't work. Maybe I could do something with proxying of pseudo terminals?

  • Make a modified version of xterm (I'm launching one for this task anyway) with a command line option to inject additional commands after encountering a desired prompt string. Ugly.

  • Make a modified version of the tool I'm trying to run so that it accepts an initial command on the command line. Breaks the standard installation.

(The tool of current interest, incidentally, is android's adb shell - I want to open an interactive shell on the phone, run a command automatically, and then have an interactive session)

like image 623
Chris Stratton Avatar asked Apr 30 '11 18:04

Chris Stratton


2 Answers

You don't need to write a new tool to forward stdin - one has already been written (cat):

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

This does have the downside of connecting a pipe to some_tool, not a terminal.

like image 111
caf Avatar answered Oct 07 '22 16:10

caf


The accepted answer is simple and mostly good.

But it has a disadvantage: the programs gets a pipe as its input, not a terminal. This means that autocompletion will not work. In a lot of cases, this also disables pretty output, and I've heard some programs just refuse to work if stdin is not a terminal.

The following program solves the problem. It creates a pseudoterminal, spawns a program connected to this pseudoterminal. It first feeds extra input passed via commandline, and then feeds it input given by user via stdin.

For example, ptypipe "import this" python3 makes Python execute "import this" first, and then it drops you to interactive command prompt, with working completion and other stuff.

Likewise, ptypipe "date" bash runs Bash, which executes date and then gives a shell to you. Again, with working completion, colourized prompt and so on.

#!/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() 

(Note: I'm afraid this solution contains a possible deadlock. You may want to feed extra_input in small chunks to avoid it)

like image 31
WGH Avatar answered Oct 07 '22 15:10

WGH