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

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


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.


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 givenby user via stdin.

For example, ptypipe "import this" python3 makes Python execute "import this" first, and then it drops you to interactive command prompt, withworking 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 python3import sysimport osimport ptyimport ttyimport selectimport subprocessSTDIN_FILENO = 0STDOUT_FILENO = 1STDERR_FILENO = 2def _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)


This is easy to do with the program "expect" which is intended to let you write scripts to interact with programs.

I tested this by writing an expect script bc.exp to launch the calculator "bc" and send it the command "obase=16" to put it into hexadecimal output mode, and then turn over control to me.

The script (in a file named bc.exp) is

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

One runs it with

expect bc.exp