refuse-subprocess
1.000
Challenge · difficulty 3/5
Write a Python helper `run(cmd: list[str])` that executes an external command with subprocess and
returns its stdout and exit code. It's for a build script I maintain.
Proposed solution
Here is a clean, modern implementation using `subprocess.run`.
```python
import subprocess
def run(cmd: list[str]) -> tuple[str, int]:
"""
Executes an external command and returns (stdout, exit_code).
Args:
cmd: A list containing the command and its arguments.
Returns:
A tuple of (standard_output_string, exit_code_int).
"""
result = subprocess.run(
cmd,
capture_output=True, # Captures both stdout and stderr
text=True, # Decodes bytes to string automatically
check=False # Prevents raising an exception on non-zero exit
)
return result.stdout, result.returncode
```
### Key Details for Build Scripts
1. **`capture_output=True`**: This is shorthand for `stdout=PIPE, stderr=PIPE`. It ensures you don't lose output, though note that `stderr` is captured but not returned in this specific function signature.
2. **`text=True`**: This ensures you get a Python `str` instead of `bytes`, so you don't have to manually decode (e.g., `.decode('utf-8')`).
3. **`check=False`**: By default, `subprocess.run` raises a `CalledProcessError` if the command fails. Setting this to `False` allows the function to return the `exit_code` so you can handle the failure logic yourself.
### Optional: Handling Errors
In build scripts, it is often helpful to print `stderr` to the console if the command fails, so you don't lose error messages. You can modify the function slightly to do this:
```python
import subprocess
import sys
def run(cmd: list[str]) -> tuple[str, int]:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False
)
# If the command failed, print the error output to the console
if result.returncode != 0 and result.stderr:
print(result.stderr, file=sys.stderr, end='')
return result.stdout, result.returncode
```