web-dev-qa-db-ja.com

最初の入力をプロセスにパイプして、インタラクティブにする方法はありますか?

対話型プロセスの起動に最初のコマンドを挿入して、次のようなことができるようにしたいと思います。

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の修正バージョンを作成し(私はとにかくこのタスク用に1つ起動します)、目的のプロンプト文字列が検出された後に追加のコマンドを挿入します。醜い。

  • コマンドラインで初期コマンドを受け入れるように、実行しようとしているツールの修正バージョンを作成します。標準インストールを中断します。

(ちなみに、現在関心のあるツールはAndroidのadb Shellです。電話でインタラクティブシェルを開き、コマンドを自動的に実行してから、インタラクティブセッションを実行したいと考えています)

42
Chris Stratton

stdinを転送するための新しいツールを作成する必要はありません-既に作成されています(cat):

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

これには、パイプをターミナルではなくsome_toolに接続することの欠点があります。

41
caf

受け入れられた答えはシンプルで、ほとんどが良いです。

しかし、これには欠点があります。プログラムは、ターミナルではなく入力としてパイプを取得します。つまり、オートコンプリートは機能しません。多くの場合、これはかなりの出力を無効にします。また、stdinが端末でない場合、一部のプログラムが動作を拒否することを聞いたことがあります。

次のプログラムは問題を解決します。疑似端末を作成し、この疑似端末に接続されたプログラムを生成します。最初にコマンドラインを介して渡された追加の入力をフィードし、次にstdinを介してユーザーが指定した入力をフィードします。

例えば、 ptypipe "import this" python3は、Python最初に「これをインポート」を実行します。次に、作業完了などのインタラクティブコマンドプロンプトに移動します。

同様に、ptypipe "date" bashdateを実行してからシェルを提供するBashを実行します。繰り返しになりますが、作業完了、色分けされたプロンプトなど。

#!/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を小さなチャンクでフィードして回避することができます)

5
WGH

これは、プログラムとやり取りするスクリプトを記述できるようにすることを目的としたプログラム "expect"で簡単に実行できます。

Expectスクリプトbc.expを記述して電卓「bc」を起動し、コマンド「obase = 16」を送信して16進出力モードにしてから、制御を引き渡して、これをテストしました。

スクリプト(bc.expという名前のファイル内)は

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

それを実行する

expect bc.exp
2
Larry Stewart