如何将初始输入传递到进程中,然后进行交互?

时间:2011-04-30 18:18:03

标签: linux unix pipe

我希望能够在启动交互式流程时注入初始命令,以便我可以这样做:

echo "initial command" | INSERT_MAGIC_HERE some_tool

tool> initial command 

[result of initial command] 

tool> [now I type an interactive command]

什么行不通:

  • 只是管道输入初始命令不起作用,因为这导致stdin没有连接到终端

  • 写入/ dev / pts / [number]将输出发送到终端,而不是像从终端那样输入到进程

什么会有缺点:

  • 创建一个分叉子命令,写入stdin,然后从自己的stdin转发所有内容。下行 - 终端控制事物(如线与字符模式)不起作用。也许我可以用代理伪终端做点什么?

  • 使用命令行选项创建xterm的修改版本(我正在为此任务启动一个),以在遇到所需的提示字符串后注入其他命令。难看。

  • 制作我正在尝试运行的工具的修改版本,以便它在命令行上接受初始命令。打破标准安装。

(当前感兴趣的工具,顺便提一下,是android的adb shell - 我想在手机上打开一个交互式shell,自动运行命令,然后进行交互式会话)

5 个答案:

答案 0 :(得分:35)

您无需编写新工具来转发stdin - 已经编写了一个(cat):

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

这确实存在将管道连接到some_tool而不是终端的缺点。

答案 1 :(得分:5)

接受的答案很简单,而且大多数都很好。

但它有一个缺点:程序将管道作为输入,而不是终端。这意味着自动完成功能不起作用。在很多情况下,这也会禁用漂亮的输出,我听说如果stdin不是终端,一些程序就会拒绝工作。

以下程序解决了这个问题。它创造了一个伪终结, 产生一个连接到这个伪终端的程序。它首先喂食 额外的输入通过命令行传递,然后输入给定的输入 用户通过stdin。

例如,ptypipe "import this" python3使Python首先执行“import this”,然后它将您带到交互式命令提示符, 工作完成和其他事情。

同样,ptypipe "date" bash运行Bash,执行date然后给你一个shell。再次,工作完成,着色提示等等。

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

(注意:我担心此解决方案可能存在死锁。您可能希望以小块的形式提供extra_input以避免它出现。)

答案 2 :(得分:2)

这很容易通过程序“期望”来完成,该程序旨在让您编写脚本来与程序进行交互。

我通过编写期望脚本bc.exp来启动计算器“ bc”并向其发送命令“ obase = 16”以使其进入十六进制输出模式,然后将控制权交给我来对此进行了测试。

脚本(在名为bc.exp的文件中)是

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

一个人用

expect bc.exp

答案 3 :(得分:0)

@caf 的回答是一个可靠的答案。

我想我会在这里用一个对我有用的相关选项来扩展它。

我的 <initial command> 实际上是一个命令列表,可以在一个文本文件中找到。因此,我想将其通过管道传输到 <some command> 并连接到 stdin

cat 处理得很好,它接受 -stdin 读取:

cat init_file - | some_command

这与 cafs 回答中讨论的限制相同。

答案 4 :(得分:-2)

也许您可以使用a here document将输入传递给abd。 E. g。像这样(使用bc做一个简单的计算作为例子)。

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

bc会话之后保持打开,因此在开始和结束标记之间(“&lt;&lt; END”和“END”之间)提供的内容将传递给命令。