Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Printing subprocess stdout lines output

I created a simple Python function:

import subprocess
from io import TextIOWrapper


def run_shell_command(command: list, debug: bool = False):
    '''
    Run shell command

    :param command: Shell command
    :param debug: Debug mode
    :return: Result code and message
    '''
    try:
        process = subprocess.run(
            command, check=True, text=True, timeout=5,
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT
        )
        if debug:
            for line in TextIOWrapper(process.stdout, encoding='utf-8'):
                print(line)
        message = 'Shell command executed sucessfully'
        return ({'code': 200, 'msg': message, 'stdout': process.stdout})
    except subprocess.CalledProcessError as e:
        return ({'code': 500, 'msg': e.output})


if __name__ == "__main__":
    command = run_shell_command(['ls', '-lah'], True)
    print(command)

When I run it in debug mode, I get the following error:

Traceback (most recent call last):
  File "/tmp/command.py", line 28, in <module>
    command = run_shell_command(['ls', '-lah'], True)
  File "/tmp/command.py", line 19, in run_shell_command
    for line in TextIOWrapper(process.stdout, encoding="utf-8"):
AttributeError: 'str' object has no attribute 'readable'

Running Python 3.9 on a Linux server, I was wondering if you can provide some insight where the issue might be. With debug disabled, I get a proper text output. Thank you for your help.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Edit: Based on the comments below, the fix is quite simple:

        if debug:
            print(process.stdout.rstrip())

>Solution :

Unfortunately "simple" doesn’t cut it with all of the corner cases involved – you’ll need to read the subprocess stdout as it’s being streamed, print it out and accumulate it in a buffer, and keep track of time so you can timeout correctly. Note that this too has a possible bug (though not very dangerous) if the 4096 byte read happens to end at a multi-line character.

import subprocess
import time


def run_shell_command(command: list, debug: bool = False, timeout: float = 5):
    """
    Run shell command

    :param command: Shell command
    :param debug: Debug mode
    :return: Result code and message
    """
    process = subprocess.Popen(
        command,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    )
    start_time = time.time()
    output = b""
    while True:
        buf = process.stdout.read(4096)
        if debug:
            # could fail if `buf` happens to end in a multi-byte character
            print(buf.decode("utf-8", "ignore"))
        output += buf

        if time.time() - start_time > timeout:
            process.kill()
            message = "Shell command timed out"
            return {"code": 500, "msg": message, "stdout": output}

        if process.poll() is not None:
            break
    if process.returncode != 0:
        message = "Shell command failed"
        return {"code": 500, "msg": message, "stdout": output}

    message = "Shell command executed successfully"
    return {"code": 200, "msg": message, "stdout": output}


if __name__ == "__main__":
    command = run_shell_command(["ls", "-lah"], True)
    print(command)
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading