简体   繁体   English

如何使用`stdin = PIPE`重现`stdin = sys.stdin`?

[英]How do I reproduce `stdin=sys.stdin` with `stdin=PIPE`?

I have the following code that works exactly as intended: 我有以下代码完全按预期工作:

from subprocess import Popen

process = Popen(
    ["/bin/bash"],
    stdin=sys.stdin,
    stdout=sys.stdout,
    stderr=sys.stderr,
)
process.wait()

I can interactively use bash, tab works, etc. 我可以交互式地使用bash,tab works等。

However, I want to control what I send to stdin, so I'd like the following to work: 但是,我想控制我发送到stdin的内容,所以我希望以下工作:

import os
import sys
from subprocess import Popen, PIPE
from select import select

process = Popen(
    ["/bin/bash"],
    stdin=PIPE,
    stdout=sys.stdout,
    stderr=sys.stderr,
)

while True:
    if process.poll() is not None:
        break

    r, _, _ = select([sys.stdin], [], [])

    if sys.stdin in r:
        stdin = os.read(sys.stdin.fileno(), 1024)
        # Do w/e I want with stdin
        os.write(process.stdin.fileno(), stdin)

process.wait()

But the behavior just isn't the same. 但这种行为并不相同。 I've tried another approach (going through a pty): 我尝试了另一种方法(通过pty):

import os
import sys
import tty
from subprocess import Popen
from select import select

master, slave = os.openpty()
stdin = sys.stdin.fileno()

try:
    tty.setraw(master)
    ttyname = os.ttyname(slave)

    def _preexec():
        os.setsid()
        open(ttyname, "r+")

    process = Popen(
        args=["/bin/bash"],
        preexec_fn=_preexec,
        stdin=slave,
        stdout=sys.stdout,
        stderr=sys.stderr,
        close_fds=True,
    )

    while True:
        if process.poll() is not None:
            break

        r, _, _ = select([sys.stdin], [], [])

        if sys.stdin in r:
            os.write(master, os.read(stdin, 1024))
finally:
    os.close(master)
    os.close(slave)

And the behavior is pretty close, except tab still doesn't work. 并且行为非常接近,除了tab仍然不起作用。 Well, tab is properly sent, but my terminal doesn't show the completion, even though it was done by bash. 好吧,tab已正确发送,但我的终端没有显示完成,即使它是由bash完成的。 Arrows also show ^[[A instead of going through history. 箭头也显示^[[A而不是通过历史记录。

Any idea? 任何的想法?

All I needed was setting my sys.stdout to raw. 我只需要将我的sys.stdout设置为raw。 I also found out 3 things: 我还发现了3件事:

  • I need to restore the terminal settings on sys.stdout 我需要恢复sys.stdout上的终端设置
  • subprocess.Popen has a start_new_session argument that does what my _preexec function is doing. subprocess.Popenstart_new_session说法,做什么我_preexec功能正在做什么。
  • select.select accepts a 4th argument, which is a timeout before giving up. select.select接受第4个参数,这是放弃之前的超时。 It lets me avoid being stuck in the select loop after exiting. 它让我避免在退出后卡在选择循环中。

Final code: 最终代码:

import os
import sys
import tty
import termios
import select
import subprocess

master, slave = os.openpty()
stdin = sys.stdin.fileno()

try:
    old_settings = termios.tcgetattr(sys.stdout)
    tty.setraw(sys.stdout)

    process = subprocess.Popen(
        args=["/bin/bash"],
        stdin=slave,
        stdout=sys.stdout,
        stderr=sys.stderr,
        close_fds=True,
        start_new_session=True,
    )

    while True:
        if process.poll() is not None:
            break

        r, _, _ = select.select([sys.stdin], [], [], 0.2)

        if sys.stdin in r:
            os.write(master, os.read(stdin, 1024))
finally:
    termios.tcsetattr(sys.stdout, termios.TCSADRAIN, old_settings)
    os.close(master)
    os.close(slave)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM