Following solution works, a bit cumbersome, but very convenient.
It uses Bash's command substitution, essentially similar to create 2 threads to consume stdout and stderr and tee them to parent's stdout and stderr.
Please replace the var cmd
with your own command, it could be any command, then try run following script (just copy all and paste to run), then wait for 5 seconds, it will automatically stop.
python3 <<'PYTHON_SCRIPT_EOF'
# change this command to yours. It may accept arguments such as $1, $2, then please pass arguments after wrapper_cmd, such as '--', 'arg1', 'arg2'
cmd = '''
echo test arg1 is "$1"
while (( ++ i <= 5 )); do
date;
echo for stderr >&2
sleep 1
done
'''
import subprocess, os
if not os.environ.get("PARENT_STDERR"):
parent_stderr = os.dup(2)
os.set_inheritable(parent_stderr, True)
os.environ["PARENT_STDERR"] = str(parent_stderr)
# the magic 2> >(tee ...) and > >(tee ...) command can make the command output stdout/stderr to parent stderr REAL TIME, yet can capture the stdout/stderr.
# See https://tldp.org/LDP/abs/html/process-sub.html
# I could replace the `> >(tee ...)` with `| tee ...` but that affect the exit code of the whole command, require set -e -o pipefail;
wrapper_cmd = '(\n' + cmd + '\n) 2> >(tee /proc/self/fd/$PARENT_STDERR >&2) > >(tee /proc/self/fd/$PARENT_STDERR)'
p = subprocess.Popen([ 'bash', '-c', wrapper_cmd, '--', 'arg1_test_value' ],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, close_fds=False)
stdout, stderr = p.communicate()
print("====captured stdout:\n", stdout)
print("====captured stderr:\n", stderr)
PYTHON_SCRIPT_EOF
The output:
test arg1 is arg1_test_value
continue: you can see it output data belongs to stdout and stderr to current terminal real time:
Tue Jul 11 12:21:13 UTC 2023
for stderr
Tue Jul 11 12:21:14 UTC 2023
for stderr
Tue Jul 11 12:21:15 UTC 2023
for stderr
Tue Jul 11 12:21:16 UTC 2023
for stderr
Tue Jul 11 12:21:17 UTC 2023
for stderr
yet can get stdout and stderr respectively:
====captured stdout:
test arg1 is arg1_test_value
Tue Jul 11 12:21:13 UTC 2023
Tue Jul 11 12:21:14 UTC 2023
Tue Jul 11 12:21:15 UTC 2023
Tue Jul 11 12:21:16 UTC 2023
Tue Jul 11 12:21:17 UTC 2023
====captured stderr:
for stderr
for stderr
for stderr
for stderr
for stderr