sh: Python Subprocess Replacement
raw JSON → 2.2.2 verified Tue May 12 auth: no python install: verified
sh is a full-fledged subprocess replacement for Python (3.8 - 3.12) that allows you to call any program on your system as if it were a function. It dynamically resolves programs from your `$PATH`, similar to Bash, and wraps them in callable Python functions, offering an intuitive way to write shell scripts in Python. The current version is 2.2.2, and it maintains an active release cadence with regular bug fixes and minor feature additions.
pip install sh Common errors
error ModuleNotFoundError: No module named 'sh' ↓
cause The 'sh' library is not installed in the Python environment being used.
fix
Install the library using pip:
pip install sh error sh.CommandNotFound: <command_name> not found ↓
cause The external command specified (e.g., 'git', 'ls') is not found in the system's PATH environment variable.
fix
Ensure the command is installed and its directory is included in your system's PATH, or provide the full path to the executable.
error sh.ErrorReturnCode_N: <command_name> returned non-zero exit status N ↓
cause The executed external command exited with a non-zero status code, signaling that it failed or encountered an error.
fix
Handle the exception by wrapping the call in a
try...except sh.ErrorReturnCode block, inspect e.stderr or e.stdout for details, or ensure the command's arguments are correct. error sh.TimeoutException: Timeout while running <command_name> ↓
cause The external command took longer to execute than the specified `_timeout` duration.
fix
Increase the
_timeout parameter when calling the sh command, or investigate why the command is taking too long to execute. error AttributeError: '_ShProc' object has no attribute 'splitlines' ↓
cause The return value of an `sh` command is a `_ShProc` object, not a plain string, and string methods like `splitlines()` must be called after explicit conversion to `str` or `bytes`.
fix
Convert the
_ShProc object to a string using str() before calling string methods, for example: str(sh.ls()).splitlines() or sh.ls().stdout.decode().splitlines(). Warnings
breaking The `sh` library is designed exclusively for Unix-like operating systems (Linux, macOS, BSDs). It relies on various Unix system calls and does not support Windows environments. ↓
fix Use `subprocess` module or alternative libraries for cross-platform compatibility, or ensure deployment on Unix-like systems only.
breaking Upgrading from `sh` 1.x to 2.x involves significant breaking changes. Users are strongly advised to consult the `MIGRATION.md` file in the project's repository or PyPI page before upgrading. ↓
fix Review `MIGRATION.md` and adapt code as necessary to align with the 2.x API.
gotcha When an executed command exits with a non-zero status code, `sh` raises an `sh.ErrorReturnCode_X` exception (where X is the exit code), rather than silently failing. This behavior must be explicitly handled with `try...except` blocks. ↓
fix Wrap command calls that might return non-zero exit codes in `try...except sh.ErrorReturnCode` blocks to handle errors gracefully.
gotcha Prior to version 2.2.2, using `async` commands in combination with `_return_cmd=True` could lead to unexpected behavior. Specifically, `await` on such a command might return `str(self)` instead of a `RunningCommand` object, and earlier versions (pre-2.2.1) had bugs where async commands with `return_cmd` did not raise exceptions correctly. ↓
fix Upgrade to `sh` 2.2.2 or later to ensure correct asynchronous behavior and error handling for commands using `_return_cmd=True`.
gotcha While `sh` provides a Pythonic wrapper, it directly executes underlying shell commands. This means many common shell footguns (e.g., issues with improper quoting, globbing, or special characters in filenames) can still affect your scripts. ↓
fix Be mindful of shell scripting best practices, especially concerning argument quoting and validation. Leverage `sh`'s list-based argument passing where possible, as it generally handles quoting automatically.
Install compatibility verified last tested: 2026-05-12
python os / libc status wheel install import disk
3.10 alpine (musl) wheel - 0.18s 18.0M
3.10 alpine (musl) - - 0.26s 18.0M
3.10 slim (glibc) wheel 1.7s 0.12s 18M
3.10 slim (glibc) - - 0.13s 18M
3.11 alpine (musl) wheel - 0.25s 19.9M
3.11 alpine (musl) - - 0.35s 19.9M
3.11 slim (glibc) wheel 1.7s 0.22s 20M
3.11 slim (glibc) - - 0.23s 20M
3.12 alpine (musl) wheel - 0.46s 11.7M
3.12 alpine (musl) - - 0.58s 11.7M
3.12 slim (glibc) wheel 1.6s 0.44s 12M
3.12 slim (glibc) - - 0.45s 12M
3.13 alpine (musl) wheel - 0.46s 11.5M
3.13 alpine (musl) - - 0.66s 11.4M
3.13 slim (glibc) wheel 1.7s 0.40s 12M
3.13 slim (glibc) - - 0.46s 12M
3.9 alpine (musl) wheel - 0.15s 17.5M
3.9 alpine (musl) - - 0.23s 17.5M
3.9 slim (glibc) wheel 1.9s 0.12s 18M
3.9 slim (glibc) - - 0.14s 18M
Imports
- sh
import sh - Command
from sh import Command - ErrorReturnCode
from sh import ErrorReturnCode_127 - bash
from sh.contrib import bash
Quickstart last tested: 2026-04-24
import sh
import os
# Get the current user
user = os.environ.get('USER', 'unknown_user')
print(f"Hello, {user}!")
# Run a simple command and capture output
output = sh.ls('-l', '/tmp')
print(f"\nls -l /tmp:\n{output}")
# Run a command with arguments
version_info = sh.python('-V')
print(f"\nPython version:\n{version_info}")
# Handle a non-zero exit code
try:
sh.false()
except sh.ErrorReturnCode_1 as e:
print(f"\nCaught expected error: {e.full_cmd}, Exit Code: {e.exit_code}")
# Pipe commands (similar to shell)
piped_output = sh.wc('-l', _in=sh.ls('-1'))
print(f"\nNumber of files in current directory: {piped_output.strip()}")