代码之家  ›  专栏  ›  技术社区  ›  CN-LanBao

如何从子进程获取标准输出。带超时检查的Popen优雅吗?

  •  0
  • CN-LanBao  · 技术社区  · 2 年前

    个人电脑系统:Ubuntu18.04
    Python版本:3.6.9
    当子进程执行类似“adb logcat”的命令时。Popen,我想获取标准输出,并根据其中的关键字决定是否停止子进程。 如果关键字长时间不出现,我也会停止。第一次尝试如下

    import time
    import subprocess
    
    cmd = "adb logcat"
    timeout = 10
    adb_shell_pipe = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, universal_newlines=True)
    start_time = time.time()
    for info in iter(adb_shell_pipe.stdout.readline, ""):
        if "keyword" in info:
            print("Find keyword!")
            # Omit kill process
            break
        if time.time() - start_time > timeout:
            print("Fail!")
            # Omit kill process
            break
    

    代码成功地完成了我需要的工作,但我发现,如果在子进程启动后的第10秒没有下一个输出,程序将不会以“Fail!”结束直到下一次输出。
    我想是因为 readline() 块来读取输出。因此,我将stdout设置为non block by fcntl 比如下面的代码

    import os
    import time
    import fcntl
    import subprocess
    
    # Command was replaced to create test scenarios
    cmd = "adb shell"
    timeout = 10
    adb_shell_pipe = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, universal_newlines=True)
    
    fd = adb_shell_pipe.stdout.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    
    count = 0
    start_time = time.time()
    while adb_shell_pipe.poll() is None:
        count += 1
        info = adb_shell_pipe.stdout.readline()
        if info and "keyword" in info:
            print("Find keyword!")
            # Omit kill process
            break
        if time.time() - start_time > timeout:
            print("Fail!")
            # Omit kill process
            break
    print(count)
    
    # Output
    Fail!
    4131304
    

    如上所示,结果与预期一致。然而,它会执行 readline() 10秒内4131304次,可能会浪费资源 文件锁 模块不能在Windows上使用。
    那么,有没有一种更优雅、更通用的方法来实现这个需求呢?

    0 回复  |  直到 2 年前
        1
  •  0
  •   Sheikh Abdul Manan    2 年前

    你可以在每次迭代中调用一个异步函数,它会在10秒后终止进程,也可以在每次迭代中终止之前的异步调用,比如

    killer_coroutine = None
    while loop:
       # stuff...
       if killer_coroutine:
          killer_coroutine.cancel()
       killer_coroutine = async_func()
    

    因此,如果循环在某个点停止,进程将被终止。 我不确定这是否有效,也许值得一试。