{"id":1024,"library":"sh","title":"sh: Python Subprocess Replacement","description":"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.","status":"active","version":"2.2.2","language":"python","source_language":"en","source_url":"https://github.com/amoffat/sh","tags":["shell","subprocess","command-line","scripting","unix"],"install":[{"cmd":"pip install sh","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Required Python version range","package":"python","optional":false}],"imports":[{"note":"Imports the main 'sh' object, from which system commands are accessed as attributes (e.g., sh.ls).","symbol":"sh","correct":"import sh"},{"note":"Useful for explicitly calling commands not in PATH or with special characters, e.g., `cmd = Command('/usr/bin/ffmpeg')`.","symbol":"Command","correct":"from sh import Command"},{"note":"Specific error codes (e.g., ErrorReturnCode_1, ErrorReturnCode_127) are raised when a command exits with a non-zero status. Catch `sh.ErrorReturnCode` for a general handler.","symbol":"ErrorReturnCode","correct":"from sh import ErrorReturnCode_127"},{"note":"Introduced in 2.1.0 for improved Bash integration.","symbol":"bash","correct":"from sh.contrib import bash"}],"quickstart":{"code":"import sh\nimport os\n\n# Get the current user\nuser = os.environ.get('USER', 'unknown_user')\nprint(f\"Hello, {user}!\")\n\n# Run a simple command and capture output\noutput = sh.ls('-l', '/tmp')\nprint(f\"\\nls -l /tmp:\\n{output}\")\n\n# Run a command with arguments\nversion_info = sh.python('-V')\nprint(f\"\\nPython version:\\n{version_info}\")\n\n# Handle a non-zero exit code\ntry:\n    sh.false()\nexcept sh.ErrorReturnCode_1 as e:\n    print(f\"\\nCaught expected error: {e.full_cmd}, Exit Code: {e.exit_code}\")\n\n# Pipe commands (similar to shell)\npiped_output = sh.wc('-l', _in=sh.ls('-1'))\nprint(f\"\\nNumber of files in current directory: {piped_output.strip()}\")","lang":"python","description":"This quickstart demonstrates basic command execution, passing arguments, capturing output, handling non-zero exit codes, and piping commands. Replace `ls` and `python` with commands available on your system."},"warnings":[{"fix":"Use `subprocess` module or alternative libraries for cross-platform compatibility, or ensure deployment on Unix-like systems only.","message":"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.","severity":"breaking","affected_versions":"All versions"},{"fix":"Review `MIGRATION.md` and adapt code as necessary to align with the 2.x API.","message":"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.","severity":"breaking","affected_versions":"1.x to 2.x migration"},{"fix":"Wrap command calls that might return non-zero exit codes in `try...except sh.ErrorReturnCode` blocks to handle errors gracefully.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Upgrade to `sh` 2.2.2 or later to ensure correct asynchronous behavior and error handling for commands using `_return_cmd=True`.","message":"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.","severity":"gotcha","affected_versions":"<2.2.2"},{"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.","message":"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.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-05-12T22:45:31.775Z","next_check":"2026-06-27T00:00:00.000Z","problems":[{"fix":"Install the library using pip: `pip install sh`","cause":"The 'sh' library is not installed in the Python environment being used.","error":"ModuleNotFoundError: No module named 'sh'"},{"fix":"Ensure the command is installed and its directory is included in your system's PATH, or provide the full path to the executable.","cause":"The external command specified (e.g., 'git', 'ls') is not found in the system's PATH environment variable.","error":"sh.CommandNotFound: <command_name> not found"},{"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.","cause":"The executed external command exited with a non-zero status code, signaling that it failed or encountered an error.","error":"sh.ErrorReturnCode_N: <command_name> returned non-zero exit status N"},{"fix":"Increase the `_timeout` parameter when calling the `sh` command, or investigate why the command is taking too long to execute.","cause":"The external command took longer to execute than the specified `_timeout` duration.","error":"sh.TimeoutException: Timeout while running <command_name>"},{"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()`.","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`.","error":"AttributeError: '_ShProc' object has no attribute 'splitlines'"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":null,"quickstart_tag":null,"pypi_latest":"2.2.2","cli_name":"","install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.18,"mem_mb":7.1,"disk_size":"18.0M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.26,"mem_mb":7.1,"disk_size":"18.0M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.7,"import_time_s":0.12,"mem_mb":7.1,"disk_size":"18M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.13,"mem_mb":7.1,"disk_size":"18M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.25,"mem_mb":8.3,"disk_size":"19.9M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.35,"mem_mb":8.3,"disk_size":"19.9M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.7,"import_time_s":0.22,"mem_mb":8.3,"disk_size":"20M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.23,"mem_mb":8.3,"disk_size":"20M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.46,"mem_mb":8.4,"disk_size":"11.7M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.58,"mem_mb":8.4,"disk_size":"11.7M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.6,"import_time_s":0.44,"mem_mb":8.4,"disk_size":"12M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.45,"mem_mb":8.4,"disk_size":"12M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.46,"mem_mb":8.9,"disk_size":"11.5M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.66,"mem_mb":8.9,"disk_size":"11.4M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.7,"import_time_s":0.4,"mem_mb":8.9,"disk_size":"12M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.46,"mem_mb":8.9,"disk_size":"12M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.15,"mem_mb":7,"disk_size":"17.5M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.23,"mem_mb":7,"disk_size":"17.5M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.9,"import_time_s":0.12,"mem_mb":7,"disk_size":"18M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.14,"mem_mb":7,"disk_size":"18M"}]},"quickstart_checks":{"last_tested":"2026-04-24","tag":null,"tag_description":null,"results":[{"runtime":"python:3.10-alpine","exit_code":0},{"runtime":"python:3.10-slim","exit_code":0},{"runtime":"python:3.11-alpine","exit_code":0},{"runtime":"python:3.11-slim","exit_code":0},{"runtime":"python:3.12-alpine","exit_code":0},{"runtime":"python:3.12-slim","exit_code":0},{"runtime":"python:3.13-alpine","exit_code":0},{"runtime":"python:3.13-slim","exit_code":0},{"runtime":"python:3.9-alpine","exit_code":0},{"runtime":"python:3.9-slim","exit_code":0}]}}