BlandCorporation
BlandCorporation

Reputation: 1354

how to handle subprocess.Popen output in both Python 2 and Python 3

I have a simple function that checks if a program is running and returns a boolean as appropriate. It does this by checking the output of the command ps -A using the module subprocess. I'm trying to get this function to work in both Python 2 and Python 3 but am encountering the following error:

TypeError: a bytes-like object is required, not 'str'

How should the function be changed such that it can work in both Python 2 and Python 3?

import subprocess

def running(program):
    results = subprocess.Popen(
        ["ps", "-A"],
        stdout = subprocess.PIPE
    ).communicate()[0].split("\n")
    matches_current = [
        line for line in results if program in line and "defunct" not in line
    ]
    if matches_current:
        return True
    else:
        return False

EDIT: Following some guidance by @Josh, I've changed the newline string delimiter to bytecode, however I'm still encountering a similar problem:

>>> import subprocess
>>> def running(program):
...     results = subprocess.Popen(
...         ["ps", "-A"],
...         stdout = subprocess.PIPE
...     ).communicate()[0].split(b"\n")
...     matches_current = [
...         line for line in results if program in line and "defunct" not in line
...     ]
...     if matches_current:
...         return True
...     else:
...         return False
... 
>>> running("top")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in running
  File "<stdin>", line 7, in <listcomp>
TypeError: a bytes-like object is required, not 'str'

Upvotes: 8

Views: 6419

Answers (3)

Kenji Noguchi
Kenji Noguchi

Reputation: 1773

universal_newlines=True produces strings instead of bytes.

results = subprocess.Popen(
        ["ps", "-A"],
        stdout = subprocess.PIPE,
        universal_newlines=True
    ).communicate()[0].split("\n")

Using iterator

p = subprocess.Popen(
        ["ps", "-A"],
        stdout = subprocess.PIPE,
        universal_newlines=True
    )
for result in p.stdin:
    print(result.strip())

Upvotes: 2

akarilimano
akarilimano

Reputation: 1074

It's an old question, but I've found a simpler way that works both in Python 2 and 3: add .decode() after communicate()[0]:

results = subprocess.Popen(
        ["ps", "-A"],
        stdout = subprocess.PIPE
    ).communicate()[0].decode().split("\n")

So you don't have to add b's before every string literal or encode() to string variables in your code. As a bonus, results will become an Unicode string.

PS. In my case I had subprocess.check_output() and appending .decode() also worked as expected.

EDIT: probably it is better to specify encoding, like decode('utf-8') just in case.

Upvotes: 3

ash
ash

Reputation: 5549

Use bytestrings like b"\n" instead of just plain "\n", and also use program.encode() instead of program (assuming you're being passed a string):

results = subprocess.Popen(
    ["ps", "-A"],
    stdout = subprocess.PIPE
).communicate()[0].split(b"\n")
matches_current = [
    line for line in results if program.encode() in line and b"defunct" not in line
]

This is because in Python 3, subprocess.Popen.communicate returns bytes rather than a string by default. In Python 2.6+, b"\n" == "\n", so you shouldn't have any problems there.

Upvotes: 3

Related Questions