Multiplex subprocess stdout/stderr to multiple destinations simultaneously.
from subprocess_multitee import Popen, tee, PIPE
import sys
# Capture output AND print to terminal in real-time
proc = Popen(['make'], stdout=tee(PIPE, sys.stdout))
output = proc.stdout.read()
proc.wait()pip install subprocess-multiteeStandard subprocess forces a choice: either capture output (stdout=PIPE) or display it (stdout=sys.stdout). This library lets you do both—plus log to files, discard with DEVNULL, or any combination.
The tee() function creates a pipe that spawns a background thread reading one byte at a time and writing to all destinations, ensuring real-time output even for line-buffered programs.
from subprocess_multitee import Popen, tee, PIPE
import sys
proc = Popen(['python', 'train.py'], stdout=tee(PIPE, sys.stdout))
output = proc.stdout.read() # Full output captured
proc.wait() # Terminal saw it in real-timewith open('build.log', 'wb') as log:
proc = Popen(['make', '-j4'],
stdout=tee(PIPE, sys.stdout, log),
stderr=tee(PIPE, sys.stderr, log))
stdout, stderr = proc.communicate()from subprocess_multitee import run, tee, PIPE
import sys
result = run(['pytest', '-v'], stdout=tee(PIPE, sys.stdout), check=True)
print(f"Captured {len(result.stdout)} bytes")from subprocess_multitee import Popen, tee, PIPE, DEVNULL
proc = Popen(['noisy-command'], stdout=tee(DEVNULL, PIPE))
output = proc.stdout.read() # Captured, but nothing printedimport subprocess
from subprocess_multitee import tee, PIPE
t = tee(PIPE, open('log.txt', 'wb'))
proc = subprocess.Popen(['cmd'], stdout=t)
proc.wait()
t.close() # Required with stdlib Popen
output = t.pipes[0].read()Creates a tee object for use as stdout or stderr in Popen.
Destinations can be any combination of:
PIPE— creates a readable pipe (accessible via.pipeslist orPopen.stdout)DEVNULL— discard output (no-op, skipped efficiently)- File objects — anything with
.write()(binary mode recommended) - File descriptors — raw
intfds - Text-mode files — automatically uses
.bufferif available
Not supported: STDOUT (use stderr=tee(...) separately)
Drop-in subclass of subprocess.Popen with tee integration:
- Automatically closes parent's write fd after fork
- Exposes first
PIPEdestination as.stdout/.stderr - Joins tee threads on context manager exit
Drop-in replacements using the enhanced Popen.
tee()creates anos.pipe()- Returns a object with
fileno()pointing to the write end - Spawns a daemon thread that reads 1 byte at a time and writes to all destinations
Popenpasses the fd to the subprocess, then closes the parent's copy- When subprocess exits, the pipe closes, thread sees EOF and exits
-
subprocess-tee — Drop-in replacement for
subprocess.runthat prints output in real-time while capturing. Simple API (tee=Falseto disable), but impliesuniversal_newlines=True(text mode only) and doesn't support multiple arbitrary destinations. -
tee-subprocess — Also replaces
subprocess.runwith tee support, but adds async/await support viaasyncio. Automatically detects async context. Supports redirecting tee output to file objects. Better static typing. More complex internals.
subprocess-multitee takes a different approach: a composable tee(*destinations) primitive that works with any number of destinations (multiple files, PIPE, DEVNULL, etc.) and a minimal Popen subclass. This gives more flexibility at the cost of slightly more verbose usage.
MIT