繁体   English   中英

如何在 python 中广泛和/或交替地从 stdout 和/或 stderr 读取并写入进程的 stdin?

[英]How to wildly and/or alternatingly read from stdout and/or stderr and write to stdin of a process in python?

我在这里搜索了很多不同的问题和答案,但我没有找到一个通用的方法:

  • 从 stdout 和 stderr 读取可用的内容 - 直到最后一个字节(当前!)可用(即使它不是 \n)
  • 根据读取的信息向标准输入写入内容
  • 命令行工具将对这个标准输入做出反应并(很久以后)写一些东西/什么都没有
  • 从头开始 - 如果进程完成则离开循环,捕获其返回码

此处可找到的大多数示例仅将 ONCE 写入 stdin,而从 stdout 和/或 stderr 仅读取 ONCE(之前/之后)。 我的意图是“编织”从标准输出和/或标准错误读取并写入标准输入! 这里有一个例子:

  • 启动命令行工具(最后带有参数) - 例如python3.exe
  • 总是从标准输出和标准错误读取
  • 例如阅读所有内容,然后从标准输出阅读>>>
  • print('Hello World.')\n
  • 例如阅读所有内容( Hello World.\n )并从标准输出阅读>>>
  • x = [6, 0]\n
  • 例如阅读所有内容,然后从标准输出阅读>>>
  • y = x[0] / x[1]\n
  • 例如读取所有内容(... ZeroDivisionError: division by zero
  • ...

我试图用这个找到的互联网示例来解决它(在其他失败的尝试之后):

# Example #27
# of https://www.programcreek.com/python/example/85342/asyncio.create_subprocess_shell
# Source Project: Python-Journey-from-Novice-to-Expert   Author: PacktPublishing   File: 07_processes.py    License: MIT License    5 votes vote downvote up
import asyncio
import sys

async def read_from_pipe(pipe, buf, timeout_sec):
    while True:
        try:
            pipe_byte = await asyncio.wait_for(pipe.read(1), timeout_sec)
        except asyncio.TimeoutError:
            break
        else:
            if len(pipe_byte) == 1:
                buf.append(pipe_byte[0])
            else:
                pipe_byte == b'\n' # in case of end of file: fake end of line 
            if pipe_byte == b'\n':
                return len(buf)

async def run_script(version):
    process = await asyncio.create_subprocess_shell(
        r'C:\Programs\Python\Python38-32\python.exe',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
        stdin=asyncio.subprocess.PIPE,
    )

    if version == 0:
        # Write a simple Python script to the interpreter
        process.stdin.write(b'\n'.join((
            b'import math',
            b'x = 2 ** 8',
            b'y = math.sqrt(x)',
            b'z = math.sqrt(y)',
            b'print("x: %d" % x)',
            b'print("y: %d" % y)',
            b'print("z: %d" % z)',
            b'for i in range(int(z)):',
            b'    print("i: %d" % i)',
        )))
        # Make sure the stdin is flushed asynchronously
        await process.stdin.drain()
        # And send the end of file so the Python interpreter will
        # start processing the input. Without this the process will
        # stall forever.
        process.stdin.write_eof()

        # Fetch the lines from the stdout asynchronously
        async for out in process.stdout:
            # Decode the output from bytes and strip the whitespace
            # (newline) at the right
            print(out.decode('utf-8').rstrip())

        # Wait for the process to exit
        await process.wait() 
    elif version == 1:
        cmds = [b'import math',
          b'x = 2 ** 8',
          b'y = math.sqrt(x)',
          b'z = math.sqrt(y)',
          # b'q = z / 0',
          b'print("x: %d" % x)',
          b'print("y: %d" % y)',
          b'print("z: %d" % z)',
          b'for i in range(int(z)):',
          b'    print("i: %d" % i)',
          b'exit(0)',
        ]
        idx = 0
        while True:
            stdout_buf = bytearray(b'')           
            out_read = await read_from_pipe(process.stdout, stdout_buf, 0.5)
            print(f'stdout[{out_read}]: {stdout_buf.decode("ascii")}\n') if out_read else None
            stderr_buf = bytearray(b'')             
            err_read = await read_from_pipe(process.stderr, stderr_buf, 0.5)
            print(f'stderr[{err_read}]: {stdout_buf.decode("ascii")}\n') if err_read else None
            if idx < len(cmds):
                current_cmd = cmds[idx].decode('ascii')
                print(f'writing command at index {idx}: "{current_cmd}"')
                process.stdin.write(cmds[idx])
                process.stdin.write(b'\n')
                await process.stdin.drain()
                process.stdin.write_eof() # tried with/without this line, afterwards program hangs
                idx += 1
            else:
                break
        await process.wait()             

if sys.platform == "win32":
    codepage = 'cp437'
    loop = asyncio.ProactorEventLoop() # For subprocess' pipes on Windows
    asyncio.set_event_loop(loop)
else:
    codepage = 'utf-8'
    loop = asyncio.get_event_loop()

version = 1 # version = 0 runs but is not alternatingly reading stdout/stderr and writing to stdin!
returncode = loop.run_until_complete(run_script(1))
print(f'done with return code = {returncode}.')

目前它不会从stdoutstderr读取任何内容。 并且在cmds中的条目写入后,程序也会挂起。 最后它应该在 linux 下运行。

如何正确编写程序?

python3.exe 是不是一个“太特殊”的命令行工具,是这些问题的根本原因吗?

提示:这个例子和解决方案根本不必是高性能的。 用于控制的预期命令行工具非常慢(总体执行时间为 20 秒到 20 分钟)。 如果(简化的)工作解决方案不需要多线程和多处理,则实际上并不需要多线程和多处理。

我发现python3.exe有点太特殊了,无法控制。 我最好在 windows 上使用例如cmd /S (我为 Linux 阅读了/bin/bash ) - 现在可以使用:

# Example #27
# of https://www.programcreek.com/python/example/85342/asyncio.create_subprocess_shell
# Source Project: Python-Journey-from-Novice-to-Expert   Author: PacktPublishing   File: 07_processes.py    License: MIT License    5 votes vote downvote up
import asyncio
import sys

async def read_from_pipe(pipe, buf, timeout_sec):
    while True:
        try:
            pipe_byte = await asyncio.wait_for(pipe.read(1), timeout_sec)
        except asyncio.TimeoutError:
            return len(buf) # no more bytes available currently on that pipe
        else:
            if len(pipe_byte) == 1:
                buf.append(pipe_byte[0])
            else:
                return len(buf) # end of pipe reached

async def run_script():
    process = await asyncio.create_subprocess_shell(
        'cmd /S',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
        stdin=asyncio.subprocess.PIPE,
    )

    cmds = [b'dir P*C*S*.*',
    b'echo %temp%',
    b'exit']
    idx = 0
    while True:
        stdout_buf = bytearray(b'')           
        out_read = await read_from_pipe(process.stdout, stdout_buf, 0.5)
        print(f'stdout[{out_read}]: {stdout_buf.decode("ascii")}\n') if out_read else None
        stderr_buf = bytearray(b'')             
        err_read = await read_from_pipe(process.stderr, stderr_buf, 0.5)
        print(f'stderr[{err_read}]: {stdout_buf.decode("ascii")}\n') if err_read else None
        if idx < len(cmds):
            current_cmd = cmds[idx].decode('ascii')
            print(f'writing command at index {idx}: "{current_cmd}"')
            process.stdin.write(cmds[idx])
            process.stdin.write(b'\n')
            await process.stdin.drain()
            idx += 1
        else:
            pass
        if process.returncode is not None:
            print(f'return code = {process.returncode}')
            return process.returncode

if sys.platform == "win32":
    codepage = 'cp437'
    loop = asyncio.ProactorEventLoop() # For subprocess' pipes on Windows
    asyncio.set_event_loop(loop)
else:
    codepage = 'utf-8'
    loop = asyncio.get_event_loop()

returncode = loop.run_until_complete(run_script())
print(f'done with return code = {returncode}.')

output 在我的电脑上:

PS C:\Git\ownPythonRepository\Python\CliTap>  c:; cd 'c:\Git\ownPythonRepository\Python\CliTap'; & 'C:\Programs\Python\Python38-32\python.exe' 'c:\Users\BitLauncher\.vscode\extensions\ms-python.python-2022.14.0\pythonFiles\lib\python\debugpy\adapter/../..\debugpy\launcher' '63136' '--' 'c:\Git\ownPythonRepository\Python\CliTap\PythonConsoleSandbox.py' 
stdout[137]: Microsoft Windows [Version 10.0.11111.2222]
(c) Microsoft Corporation. All rights reserved.

C:\Git\ownPythonRepository\Python\CliTap>

 stdout[340]: dir P*C*S*.*
 Volume in drive C is What
 Volume Serial Number is 9999-9999

 Directory of C:\Git\ownPythonRepository\Python\CliTap

2022-09-26  23:52             2,365 PythonConsoleSandbox.py
               1 File(s)          2,365 bytes
               0 Dir(s)  99,999,999,999 bytes free

C:\Git\ownPythonRepository\Python\CliTap>
writing command at index 1: "echo %temp%"
stdout[93]: echo %temp%
C:\Users\BitLau~1\AppData\Local\Temp

C:\Git\ownPythonRepository\Python\CliTap>

writing command at index 2: "exit"
stdout[5]: exit


return code = 1
done with return code = 1.
PS C:\Git\ownPythonRepository\Python\CliTap> 

就是这样 - 现在我将能够根据 stdout 和/或 stderr 特定命令向 stdin 写入......太好了。 稍后我可以通过多线程改进它:-) 如果需要。

暂无
暂无

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

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