¿Cómo se puede silenciar de manera general la salida del terminal de ejecutables ejecutados por funciones de Python?
console executable (2)
Quiero suprimir toda la salida del terminal producida por una función que ejecuta ejecutables.
Intenté suprimir la salida de una función de Python utilizando un administrador de contexto que redefine temporalmente stdout y stderr cada vez que se llama a la función.
Esto suprime la salida del terminal producida por
print
llamadas de
print
en la función, pero no parece funcionar cuando la función llama a los ejecutables que producen la salida del terminal.
Entonces, ¿cómo podría suprimirse la salida de ejecutables llamados por las funciones de Python?
Mi codigo esta abajo.
He incluido una función de ejemplo que llama a
ls
para intentar ilustrar el tipo de salida de terminal que quiero suprimir (aunque la función con la que estoy tratando es diferente).
#!/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()
En mi caso particular, estoy tratando de ejecutar la siguiente función (en lugar de la función
ls
anterior):
with propyte.silence():
stream = pyaudio.PyAudio().open(
format = pyaudio.PyAudio().get_format_from_width(1),
channels = 1,
rate = bitrate,
output = True
)
Cuando se ejecuta, esto produce resultados como los siguientes:
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
Quiero suprimir esa salida.
EDITAR: probar una solución proporcionada por @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()
No he tenido éxito en suprimir la salida del terminal de la
print
o el
ls
y no estoy seguro de por qué.
Para los errores de ALSA en particular, puede usar la función
snd_lib_error_set_handler
de ALSA como se describe, por ejemplo,
en esta pregunta
.
Para uno de mis proyectos, creé un módulo llamado
mute_alsa
, que se ve así:
import ctypes
ERROR_HANDLER_FUNC = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_int,
ctypes.c_char_p, ctypes.c_int,
ctypes.c_char_p)
def py_error_handler(filename, line, function, err, fmt):
pass
c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)
try:
asound = ctypes.cdll.LoadLibrary(''libasound.so.2'')
asound.snd_lib_error_set_handler(c_error_handler)
except OSError:
pass
Y lo usa simplemente poniendo lo siguiente en su código:
import mute_alsa
En un sentido más general, puede evitar que
cualquier cosa se
imprima en
stderr
simplemente cerrando el descriptor de archivo asociado.
Por ejemplo, si intentamos
rm
un archivo que no existe, se imprime un mensaje de error en
stderr
:
>>> import sys
>>> import os
>>> os.system(''rm /doesnotexist'')
rm: cannot remove ‘/doesnotexist’: Is a directory
256
Si
stderr
el descriptor de archivo
stderr
:
>>> os.close(sys.stderr.fileno())
Ya no vemos ningún mensaje de error:
>>> os.system(''rm /doesnotexist'')
La desventaja de este enfoque es que silencia
todos los
mensajes para
stderr
, lo que significa que ya no verá mensajes de error legítimos.
Esto puede llevar a modos de falla sorprendentes (¡el programa salió sin imprimir ningún error!).
Puede cambiar de PyAudio al módulo de dispositivo de sounddevice , que ya se encarga de silenciar la salida del terminal (ver #12 ). Así es como se hace allí (usando 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)
Si desea una solución pura de Python, puede probar este administrador de contexto:
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)
Esta es una publicación de blog muy útil sobre el tema: http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ .
ACTUALIZAR:
El administrador de contexto anterior silencia la salida de error estándar (
stderr
), que se utiliza para los mensajes molestos de PortAudio mencionados en la pregunta original.
Para deshacerse de la salida estándar (
stdout
), como en su pregunta actualizada, deberá reemplazar
sys.stderr
con
sys.stdout
y el descriptor de archivo
2
con el número
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)