簡體   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