Python函数运行的可执行文件的终端输出如何以一般方式静音?

2022-01-11 00:00:00 python executable console pyaudio silent

问题描述

我想禁止运行可执行文件的函数产生的所有终端输出.

I want to suppress all of the terminal output produced by a function that runs executables.

我试图通过使用上下文管理器来抑制 Python 函数的输出,该上下文管理器在每次调用函数时都会临时重新定义 stdout 和 stderr.这会抑制函数中 print 调用产生的终端输出,但当函数调用产生终端输出的可执行文件时,它似乎不起作用.

I have attempted to suppress the output of a Python function by using a context manager that temporarily redefines stdout and stderr each time the function is called. This suppresses terminal output produced by print calls in the function, but it doesn't seem to work when the function calls executables that produce terminal output.

那么,如何抑制 Python 函数调用的可执行文件的输出呢?

So, how could the output of executables called by Python functions be suppressed?

我的代码如下.我已经包含了一个示例函数,它调用 ls 来尝试说明我想要抑制的终端输出类型(尽管我正在处理的函数不同).

My code is below. I have included an example function that calls ls to try to illustrate the kind of terminal output I want to suppress (though the function I'm dealing with is different).

#!/usr/bin/env python

import os
import subprocess
import sys

def main():

    print("hello")

    with silence():
        print("there")

    print("world")

    with silence():
        engage_command(command = "ls")

class silence(object):

    def __init__(
        self,
        stdout = None,
        stderr = None
        ):
        if stdout == None and stderr == None:
            devnull = open(os.devnull, "w")
            stdout = devnull
            stderr = devnull
        self._stdout = stdout or sys.stdout
        self._stderr = stderr or sys.stderr

    def __enter__(
        self
        ):
        self.old_stdout = sys.stdout
        self.old_stderr = sys.stderr
        self.old_stdout.flush()
        self.old_stderr.flush()
        sys.stdout = self._stdout
        sys.stderr = self._stderr

    def __exit__(
        self,
        exc_type,
        exc_value,
        traceback
        ):
        self._stdout.flush()
        self._stderr.flush()
        sys.stdout = self.old_stdout
        sys.stderr = self.old_stderr

def engage_command(
    command = None
    ):
    process = subprocess.Popen(
        [command],
        shell      = True,
        executable = "/bin/bash")
    process.wait()
    output, errors = process.communicate()
    return output

if __name__ == "__main__":
    main()

在我的特殊情况下,我正在尝试运行以下函数(而不是上面的 ls 函数):

In my particular case, I'm trying to run the following function (instead of the ls function above):

with propyte.silence():
    stream = pyaudio.PyAudio().open(
        format   = pyaudio.PyAudio().get_format_from_width(1),
        channels = 1, 
        rate     = bitrate, 
        output   = True
    )

运行时,会产生如下输出:

When run, this produces output like the following:

ALSA lib pcm_dsnoop.c:606:(snd_pcm_dsnoop_open) unable to open slave
ALSA lib pcm_dmix.c:1029:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm_dmix.c:1029:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for 4294967295, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for 4294967295, skipping unlock

我想抑制那个输出.

测试@Matthias 提供的解决方案

testing a solution provided by @Matthias

#!/usr/bin/env python

import contextlib
import os
import subprocess
import sys

def main():

    print("hello")

    with silence():
        print("there")

    print("world")

    with silence():
        engage_command(command = "ls")

@contextlib.contextmanager
def silence():
    devnull = os.open(os.devnull, os.O_WRONLY)
    old_stderr = os.dup(2)
    sys.stderr.flush()
    os.dup2(devnull, 2)
    os.close(devnull)
    try:
        yield
    finally:
        os.dup2(old_stderr, 2)
        os.close(old_stderr)

def engage_command(
    command = None
    ):
    process = subprocess.Popen(
        [command],
        shell      = True,
        executable = "/bin/bash")
    process.wait()
    output, errors = process.communicate()
    return output

if __name__ == "__main__":
    main()

我没有成功抑制 printls 的终端输出,我不知道为什么.

I have not been successful in suppressing the terminal output from the print or the ls and I'm not sure why.


解决方案

你可以从 PyAudio 切换到 sounddevice 模块,它已经负责使终端输出静音(参见 #12).这是在那里完成的(使用 CFFI):

You could switch from PyAudio to the sounddevice module, which already takes care of silencing the terminal output (see #12). This is how it is done there (using CFFI):

from cffi import FFI
import os

ffi = FFI()
ffi.cdef("""
/* from stdio.h */
FILE* fopen(const char* path, const char* mode);
int fclose(FILE* fp);
FILE* stderr;  /* GNU C library */
FILE* __stderrp;  /* Mac OS X */
""")
try:
    stdio = ffi.dlopen(None)
    devnull = stdio.fopen(os.devnull.encode(), b'w')
except OSError:
    return
try:
    stdio.stderr = devnull
except KeyError:
    try:
        stdio.__stderrp = devnull
    except KeyError:
        stdio.fclose(devnull)

如果你想要一个纯 Python 的解决方案,你可以试试这个上下文管理器:

If you want a pure Python solution, you can try this context manager:

import contextlib
import os
import sys

@contextlib.contextmanager
def ignore_stderr():
    devnull = os.open(os.devnull, os.O_WRONLY)
    old_stderr = os.dup(2)
    sys.stderr.flush()
    os.dup2(devnull, 2)
    os.close(devnull)
    try:
        yield
    finally:
        os.dup2(old_stderr, 2)
        os.close(old_stderr)

这是一篇关于该主题的非常有用的博客文章:http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/.

This is a very helpful blog post about the topic: http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/.

更新:

上面的上下文管理器使标准错误输出 (stderr) 静音,该输出用于原始问题中提到的来自 PortAudio 的烦人消息.要摆脱标准输出(stdout),就像您更新的问题一样,您必须将 sys.stderr 替换为 sys.stdout 和文件描述符 2 与数字 1:

The context manager above silences the standard error output (stderr), which is used for the annoying messages from PortAudio mentioned in the original question. To get rid of the standard output (stdout), as in your updated question, you'll have to replace sys.stderr with sys.stdout and the file descriptor 2 with the number 1:

@contextlib.contextmanager
def ignore_stdout():
    devnull = os.open(os.devnull, os.O_WRONLY)
    old_stdout = os.dup(1)
    sys.stdout.flush()
    os.dup2(devnull, 1)
    os.close(devnull)
    try:
        yield
    finally:
        os.dup2(old_stdout, 1)
        os.close(old_stdout)

相关文章