每个子进程打印的终端分区 [英] terminal partitioning for each of subprocesses prints

查看:35
本文介绍了每个子进程打印的终端分区的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我们有多个子进程,如下所示,其中一些结果实时打印到 sys.stdout 或 sys.stderr.

Say we have multiple subprocesses like the following which has some results printed in real time to sys.stdout or sys.stderr.

proc1 = subprocess.Popen(['cmd1'],
                         env=venv1,
                         stdout=sys.stdout,
                         stderr=sys.stderr, 
                         )

proc2 = subprocess.Popen(['cmd2'],
                         env=venv2,
                         stdout=sys.stdout,
                         stderr=sys.stderr, 
                         )

但是,在终端执行这个脚本后,在查看打印的内容时,不容易区分哪个打印来自第一个进程,哪个来自第二个进程.

However, after executing this script in the terminal, while looking at what is being printed, it is not easy to distinguish which print is from the first process and which is from the second.

是否有解决方案可以单独查看每个进程的标准输出,例如是否可以对终端屏幕进行分区并且每个分区都会显示每个进程的打印结果?

Is there a solution for this to see the stdout of each process separately, like if the terminal screen could be partitioned and each partition would have shown the printing results from each process?

推荐答案

我已经为您编写了一个 Curses 应用程序,它将执行您的要求:将终端窗口划分为多个分区,然后观察不同的输出流不同的分区.

I have written for you a curses application which will do what you request: divide the terminal window into a number of partitions and then watch the different output streams in the different partitions.

函数watch_fd_in_panes 将获取一个列表列表,其中子列表指定要在每个分区内监视哪些文件描述符.

The function watch_fd_in_panes will take a list of lists, where the sub-lists specify which file descriptors to watch inside each partition.

您的示例调用代码如下所示:

Here is what your example calling code will look like:

import subprocess
from watcher import watch_fds_in_panes

proc1 = subprocess.Popen('for i in `seq 30`; do date; sleep 1 ; done',
                         shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         )

# this process also writes something on stderr
proc2 = subprocess.Popen('ls -l /asdf; for i in `seq 20`; do echo $i; sleep 0.5; done',
                         shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         )

proc3 = subprocess.Popen(['echo', 'hello'],
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         )

try:
    watch_fds_in_panes([[proc1.stdout.fileno(), proc1.stderr.fileno()],
                        [proc2.stdout.fileno(), proc2.stderr.fileno()],
                        [proc3.stdout.fileno(), proc3.stderr.fileno()]],
                       sleep_at_end=3.)
except KeyboardInterrupt:
    print("interrupted")
    proc1.kill()
    proc2.kill()
    proc3.kill()

要运行它,您需要以下两个文件:

To run it you will need these two files:

panes.py

import curses

class Panes:
    """
    curses-based app that divides the screen into a number of scrollable
    panes and lets the caller write text into them
    """

    def start(self, num_panes):
        "set up the panes and initialise the app"

        # curses init
        self.num = num_panes
        self.stdscr = curses.initscr()
        curses.noecho()
        curses.cbreak()

        # split the screen into number of panes stacked vertically,
        # drawing some horizontal separator lines
        scr_height, scr_width = self.stdscr.getmaxyx()
        div_ys = [scr_height * i // self.num for i in range(1, self.num)]
        for y in div_ys:
            self.stdscr.addstr(y, 0, '-' * scr_width)
        self.stdscr.refresh()

        # 'boundaries' contains y coords of separator lines including notional
        # separator lines above and below everything, and then the panes
        # occupy the spaces between these
        boundaries = [-1] + div_ys + [scr_height]
        self.panes = []
        for i in range(self.num):
            top = boundaries[i] + 1
            bottom = boundaries[i + 1] - 1
            height = bottom - top + 1
            width = scr_width
            # create a scrollable pad for this pane, of height at least
            # 'height' (could be more to retain some scrollback history)
            pad = curses.newpad(height, width)
            pad.scrollok(True)
            self.panes.append({'pad': pad,
                               'coords': [top, 0, bottom, width],
                               'height': height})

    def write(self, pane_num, text):
        "write text to the specified pane number (from 0 to num_panes-1)"

        pane = self.panes[pane_num]
        pad = pane['pad']
        y, x = pad.getyx()
        pad.addstr(y, x, text)
        y, x = pad.getyx()
        view_top = max(y - pane['height'], 0)
        pad.refresh(view_top, 0, *pane['coords'])

    def end(self):
        "restore the original terminal behaviour"

        curses.nocbreak()
        self.stdscr.keypad(0)
        curses.echo()
        curses.endwin()

watcher.py

import os
import select
import time

from panes import Panes


def watch_fds_in_panes(fds_by_pane, sleep_at_end=0):
    """
    Use panes to watch output from a number of fds that are writing data.

    fds_by_pane contains a list of lists of fds to watch in each pane.
    """
    panes = Panes()
    npane = len(fds_by_pane)
    panes.start(npane)
    pane_num_for_fd = {}
    active_fds = []
    data_tmpl = {}
    for pane_num, pane_fds in enumerate(fds_by_pane):
        for fd in pane_fds:
            active_fds.append(fd)
            pane_num_for_fd[fd] = pane_num
            data_tmpl[fd] = bytes()
    try:
        while active_fds:
            all_data = data_tmpl.copy()
            timeout = None
            while True:
                fds_read, _, _ = select.select(active_fds, [], [], timeout)
                timeout = 0
                if fds_read:
                    for fd in fds_read:
                        data = os.read(fd, 1)
                        if data:
                            all_data[fd] += data
                        else:
                            active_fds.remove(fd)  # saw EOF
                else:
                    # no more data ready to read
                    break
            for fd, data in all_data.items():
                if data:
                    strng = data.decode('utf-8')
                    panes.write(pane_num_for_fd[fd], strng)
    except KeyboardInterrupt:
        panes.end()
        raise

    time.sleep(sleep_at_end)
    panes.end()

最后,这是上面代码的截图:

Finally, here is a screenshot of the above code in action:

在本例中,我们同时监视相关分区中每个进程的 stdout 和 stderr.在屏幕截图中,proc2 在循环开始之前写入 stderr 的行(关于 /asdf)出现在 proc2 在循环的第一次迭代期间写入 stdout 的第一行之后(即1 已从分区顶部滚动),但这是无法控制的,因为它们被写入不同的管道.

In this example, we are monitoring both stdout and stderr of each process in the relevant partition. In the screenshot, the line that proc2 wrote to stderr before the start of the loop (regarding /asdf) has appeared after the first line that proc2 wrote to stdout during the first iteration of the loop (i.e. the 1 which has since scrolled off the top of the partition), but this is cannot be controlled because they were written to different pipes.

这篇关于每个子进程打印的终端分区的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆