122

I have found documentation about subprocess.check_output() but I cannot find one with arguments and the documentation is not very in depth. I am using Python 3 (but am trying to run a Python 2 file through Python 3)

I am trying to run this command: python py2.py -i test.txt

-i is a positional argument for argparse, test.txt is what the -i is, py2.py is the file to run

I have tried a lot of (non working) variations including: py2output = subprocess.check_output([str('python py2.py '),'-i', 'test.txt'])

py2output = subprocess.check_output([str('python'),'py2.py','-i', test.txt'])

JOHANNES_NYÅTT
  • 3,215
  • 7
  • 20
  • 24
  • 3
    What exactly happened when they didn't work? – khagler Dec 29 '12 at 02:17
  • 1
    Trying to run a Python 2 file through Python 3? No, not gonna happen. Python 3 is intentionally not backwards-compatible with Python 2. You'd have to run `2to3` for *starters* to get your file to work in Python 3. – Makoto Dec 29 '12 at 02:18
  • 1
    @Makoto: He's trying to run the Python 2 interpreter from within a Python 3 script, which is perfectly reasonable. (And from his previous question, happen to know that he's specifically doing it as a workaround for exactly the problems you're thinking of, but that isn't relevant here.) – abarnert Dec 29 '12 at 02:36
  • Show us your py2.py script, what gets printed, and what ends up in py2output (assuming it returns instead of raising). – abarnert Dec 29 '12 at 02:50
  • 1
    Also, the whole thing about Python 2, and about how you expect `py2.py` to interpret the arguments, is irrelevant to the question. It confused at least one person (Makoto) who probably would otherwise have given you a good answer, and probably confused or scared off others too. In the future, you'll probably get better answers if you can provide a minimal example, with no extraneous distractions. – abarnert Dec 29 '12 at 02:52
  • +1 on that last comment. When I test Python programs using `subprocess` I tend to use the Unix built-in `yes` because it's very light weight and generates an infinite number of lines all containing the single character "y". – Tom Swirly Dec 15 '15 at 20:55

4 Answers4

104

The right answer (using Python 2.7 and later, since check_output() was introduced then) is:

py2output = subprocess.check_output(['python','py2.py','-i', 'test.txt'])

To demonstrate, here are my two programs:

py2.py:

import sys
print sys.argv

py3.py:

import subprocess
py2output = subprocess.check_output(['python', 'py2.py', '-i', 'test.txt'])
print('py2 said:', py2output)

Running it:

$ python3 py3.py
py2 said: b"['py2.py', '-i', 'test.txt']\n"

Here's what's wrong with each of your versions:

py2output = subprocess.check_output([str('python py2.py '),'-i', 'test.txt'])

First, str('python py2.py') is exactly the same thing as 'python py2.py'—you're taking a str, and calling str to convert it to an str. This makes the code harder to read, longer, and even slower, without adding any benefit.

More seriously, python py2.py can't be a single argument, unless you're actually trying to run a program named, say, /usr/bin/python\ py2.py. Which you're not; you're trying to run, say, /usr/bin/python with first argument py2.py. So, you need to make them separate elements in the list.

Your second version fixes that, but you're missing the ' before test.txt'. This should give you a SyntaxError, probably saying EOL while scanning string literal.

Meanwhile, I'm not sure how you found documentation but couldn't find any examples with arguments. The very first example is:

>>> subprocess.check_output(["echo", "Hello World!"])
b'Hello World!\n'

That calls the "echo" command with an additional argument, "Hello World!".

Also:

-i is a positional argument for argparse, test.txt is what the -i is

I'm pretty sure -i is not a positional argument, but an optional argument. Otherwise, the second half of the sentence makes no sense.

dave_k_smith
  • 655
  • 1
  • 7
  • 22
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • now I tried with 2 arguments in argparse. I tried to put this command into subprocess: `python py2.py -i test.txt -l ong` I tried this subprocess based on your answer: `py2output=subprocess.check_output(["python","py2.py","-i","test.txt","-l","ong"])` but it doesn't work. – JOHANNES_NYÅTT Dec 29 '12 at 15:06
  • 2
    @user1925847: What do you mean by "it doesn't work"? That's not a helpful comment. If you do it right, it works—as my answer demonstrates. So obviously you've done something wrong. But it's impossible to guess what. As I said in a comment on the main question: show us your scripts, and tell us what the expected and actual output is, or there's no hope of guessing what you're doing wrong. – abarnert Dec 31 '12 at 19:17
  • 1
    and don't forget to catch CalledProcessError **and** OSError exceptions. – anatoly techtonik Nov 07 '14 at 07:56
  • 1
    I recommend using the function shlex.split("python py2.py -i test.txt") to split a string into a list of arguments. – compie Dec 16 '15 at 08:57
  • question: how come `subprocess.check_output(['cd', 'C:\'])` doesn't work? I get "FileNotFoundError: [WinError 2] The system cannot find the file specified". – Adrian Keister Apr 16 '18 at 20:51
  • @AdrianKeister Why are you asking this as a comment to a completely different question that just happens to mention `subprocess`? I'm 99% sure your question already exists, as its own question, on SO. But briefly: `subprocess` runs a program. There's no program named `cd`. There's a built-in command in the `cmd` command-line interpreter shell (aka "DOS prompt") called `cd`, which changes the current directory for that shell. You can run a new shell to execute `cd` on that shell (with `['cmd', '/c', 'cd', path]` or by using `shell=True`), but that would be pretty pointless. – abarnert Apr 16 '18 at 20:54
  • @AdrianKeister Meanwhile, if you want to change the current working directory for your program (and other subprocesses you launch), just use `os.chdir`. – abarnert Apr 16 '18 at 20:54
63

Since Python 3.5, subprocess.run is recommended instead of subprocess.check_output:

>>> subprocess.run(['cat','/tmp/text.txt'], check=True, stdout=subprocess.PIPE).stdout
b'First line\nSecond line\n'

Since Python 3.7, instead of the above, you can use capture_output=true parameter to capture stdout and stderr:

>>> subprocess.run(['cat','/tmp/text.txt'], check=True, capture_output=True).stdout
b'First line\nSecond line\n'

Also, you may want to use universal_newlines=True or its equivalent since Python 3.7 text=True to work with text instead of binary:

>>> stdout = subprocess.run(['cat', '/tmp/text.txt'], check=True, capture_output=True, text=True).stdout
>>> print(stdout)
First line
Second line
Gohu
  • 803
  • 8
  • 11
15

Adding on to the one mentioned by @abarnert

a better one is to catch the exception

import subprocess
try:
    py2output = subprocess.check_output(['python', 'py2.py', '-i', 'test.txt'],stderr= subprocess.STDOUT)  
    #print('py2 said:', py2output)
    print "here"
except subprocess.CalledProcessError as e:
    print "Calledprocerr"

this stderr= subprocess.STDOUT is for making sure you dont get the filenotfound error in stderr- which cant be usually caught in filenotfoundexception, else you would end up getting

python: can't open file 'py2.py': [Errno 2] No such file or directory

Infact a better solution to this might be to check, whether the file/scripts exist and then to run the file/script

ravi.zombie
  • 1,482
  • 1
  • 20
  • 23
1

@ravi.zombie's answer adapted to work for python 3 and produce output even when there is an non zero exit code error:

try:
    out = subprocess.check_output(['git','commit','-m', commit_message], stderr= subprocess.STDOUT ).decode()
except subprocess.CalledProcessError as e:
    print ('[ERROR]: Exit code != 0')
    out = e.output.decode()
print(out)
ntg
  • 12,950
  • 7
  • 74
  • 95