How to suppress or capture the output of subprocess.run()?
Asked Answered
G

2

222

From the examples in docs on subprocess.run() it seems like there shouldn't be any output from

subprocess.run(["ls", "-l"])  # doesn't capture output

However, when I try it in a python shell the listing gets printed. I wonder if this is the default behaviour and how to suppress the output of run().

Gileadite answered 15/12, 2016 at 19:21 Comment(8)
#8529890Lepton
subprocess.run() doesn't capture stdout or stderr by default, to do so requires passing PIPEs for the stdout and/or stderr arguments (it's right in the linked documentation). So, unless you do, they will display as they normally would from the other process.Arel
Do you want to suppress the output or capture it?Or
@SethMMorton: right now I just need to hide it.Gileadite
This question has been marked as a duplicate, but I think this is a mistake, because the API changed significantly between Python 2.7 (process.call), and Python 3.5 (process.run). I came here explicitly looking for the Python 3 answer to this question. The accepted answer for this question, and the top comment on it, are both more useful in that context than the answers on the other question.Davison
Since Python 3.7 you can also just use the capture_output=True parameter.Mart
Agree that this question shouldn't be marked as duplicated. subprocess.run is the only high-level API recommended in the subprocess module since Python 3.5.Cheboksary
What do y'all mean by capture? Where does the output go when I capture it?Kunlun
O
373

Suppressing

Here is how to suppress output, in order of decreasing levels of cleanliness. They assume you are on Python 3.

  1. You can redirect to the special subprocess.DEVNULL target.
import subprocess

# To redirect stdout (only):
subprocess.run(
    ['ls', '-l'],
    stdout = subprocess.DEVNULL
)

# to redirect stderr to /dev/null as well:
subprocess.run(
    ['ls', '-l'],
    stdout = subprocess.DEVNULL,
    stderr = subprocess.DEVNULL
)

# Alternatively, you can merge stderr and stdout streams and redirect
# the one stream to /dev/null
subprocess.run(
    ['ls', '-l'],
    stdout = subprocess.DEVNULL,
    stderr = subprocess.STDOUT
)
  1. If you want a fully manual method, can redirect to /dev/null by opening the file handle yourself. Everything else would be identical to method #1.
import os
import subprocess

with open(os.devnull, 'w') as devnull:
    subprocess.run(
        ['ls', '-l'],
        stdout = devnull
    )

Capturing

Here is how to capture output (to use later or parse), in order of decreasing levels of cleanliness. They assume you are on Python 3.

NOTE: The below examples use universal_newlines=True (Python <= 3.6).

  • This causes the STDOUT and STDERR to be captured as str instead of bytes.
    • Omit universal_newlines=True to get bytes data
  • Python >= 3.7 accepts text=True as a short form for universal_newlines=True
  1. If you simply want to capture both STDOUT and STDERR independently, AND you are on Python >= 3.7, use capture_output=True.
import subprocess

result = subprocess.run(
    ['ls', '-l'],
    capture_output = True, # Python >= 3.7 only
    text = True # Python >= 3.7 only
)
print(result.stdout)
print(result.stderr)
  1. You can use subprocess.PIPE to capture STDOUT and STDERR independently. This works on any version of Python that supports subprocess.run.
import subprocess

result = subprocess.run(
    ['ls', '-l'],
    stdout = subprocess.PIPE,
    universal_newlines = True # Python >= 3.7 also accepts "text=True"
)
print(result.stdout)

# To also capture stderr...
result = subprocess.run(
    ['ls', '-l'],
    stdout = subprocess.PIPE,
    stderr = subprocess.PIPE,
    universal_newlines = True # Python >= 3.7 also accepts "text=True"
)
print(result.stdout)
print(result.stderr)

# To mix stdout and stderr into a single string
result = subprocess.run(
    ['ls', '-l'],
    stdout = subprocess.PIPE,
    stderr = subprocess.STDOUT,
    universal_newlines = True # Python >= 3.7 also accepts "text=True"
)
print(result.stdout)
Or answered 15/12, 2016 at 20:32 Comment(12)
Note that from Python ver. 3.3 there is actually a subprocess.DEVNULL, so stdout argument can be assigned directly without the open, just using stdout=subprocess.DEVNULL.Luster
I'm getting NameError: name 'devnull' is not defined on Python 3.7Topographer
@Sabrina Have you defined devnull?Or
Is it possible to print the output while simultaneously capturing the data in a pipe (to use later or parse)?Noonberg
@Noonberg If you don't care about printing while the program is running, you can just print result.stdout after-the-fact. If you want to capture and print real-time, it's a bit more complicated because you cannot use subprocess.run, you need the lower-level subprocess.Popen. I have used something like this answer when I need to do that (adjust syntax from answer for Python 3).Or
Note that despite your answer accidentally making it look like the opposite is true, your subprocess.PIPE examples above DO work with Python < 3.7, such as Python 3.6.Jule
I've edited your answer and noted that in your answer.Jule
Huh, I never would have read that as implying they did not work on < 3.7. But, I’m glad you made it that much clearerOr
i;m only getting <_io.BufferedReader name=3> as an outputStreetlight
i was runing my flask server in one subprocess in the bg and then starting tests in another subprocess but the outcome was a nightmare! doing subprocess.run(['my', 'process'], stdout=subprocess.DEVNULL) did let my server process work on the background while the second subprocess test run at the same time with out clogging the outputs/prints, and if something goes wrong with the first subprocess(the server) i will still get that print. super thanks for the examples!Variable
Note that while the PIPE works in Python < 3.7, there is no keyword text, so you can only get a bytestring by default and the code above will crashGeneralist
@Generalist That is why the answer has these statements: "text=True is Python >= 3.7 only, use universal_newlines=True on Python <= 3.6. universal_newlines=True is identical to text=True but more verbose to type but should exist on all Python versions"Or
M
29

ex: to capture the output of ls -a

import subprocess
ls = subprocess.run(['ls', '-a'], capture_output=True, text=True).stdout.strip("\n")
print(ls)
Mcgowan answered 4/11, 2020 at 13:7 Comment(4)
this works for python 3.7, though not for python 3.6 (I get TypeError: __init__() got an unexpected keyword argument 'capture_output')Neddie
the last part helps get a clean output to a variable.Defloration
How does`t work in Flask?Darra
@Алексей I'm not sure what you mean. This should work fine in Flask, which is still running in a plain Python script and has access to the filesystem, same as any other Python script without Flask.Moyer

© 2022 - 2024 — McMap. All rights reserved.