Before:
result = subprocess.run(["git", "status"], capture_output=True, text=True, check=True)
print(result.stdout)
After:
from zap import run
print(run("git status"))
Install
pip install zap-sh
Quick Start
from zap import run
# Run a command — returns a Result object
result = run("git status")
print(result.stdout) # raw stdout
print(result.ok) # True if exit code 0
print(result.code) # exit code
print(result.lines) # stdout as list of lines
# Just print it — str() returns stdout
print(run("ls -la"))
Pipe Chaining
# Pipe with strings — clean and simple
result = run("cat logs.txt") | "grep ERROR" | "wc -l"
print(result) # number of error lines
Live Output
See output in real-time while still capturing it. No more staring at a blank screen during pip install or docker build.
# Stream output live to terminal — still captured in result
result = run("pip install flask", live=True)
# Works with long builds too
run("docker build -t myapp .", live=True)
run("make all", live=True)
Retry
Automatically retry flaky commands — network calls, API requests, CI scripts.
# Retry up to 3 times with 2s delay between attempts
result = run("curl -f https://api.example.com/health", retries=3, delay=2)
# Only retries on failure (non-zero exit) — succeeds immediately if ok
run("flaky-deploy-script.sh", retries=5, delay=5)
Check If a Command Exists
from zap import which
# Returns the path if found, None if not
if which("docker"):
run("docker ps")
else:
print("Docker not installed")
# Great for setup scripts
for cmd in ["git", "node", "python3"]:
path = which(cmd)
print(f"{cmd}: {path or 'NOT FOUND'}")
Temporary Directory
from zap import cd
# Change directory temporarily — auto-restores when done
with cd("/my/project"):
run("git status")
run("npm install")
run("npm run build")
# Back to original directory here
# Safe even if something errors
with cd("/tmp"):
run("some-command") # if this fails, directory still restores
Error Handling
from zap import run, ZapError
# Raises ZapError on non-zero exit (default)
try:
run("git push origin fake-branch")
except ZapError as e:
print(e.stderr)
print(e.code)
# Disable with check=False
result = run("might-fail", check=False)
if not result.ok:
print("Failed:", result.stderr)
Options
# Timeout (seconds)
run("sleep 100", timeout=5) # raises TimeoutError
# Working directory
run("ls", cwd="/tmp")
# Environment variables (merged with os.environ)
run("echo $API_KEY", env={"API_KEY": "secret"})
# Feed stdin
run("cat", stdin="hello from stdin")
# Safe mode — pass a list instead of string
run(["echo", "no shell injection"])
Async
from zap import run_async
result = await run_async("git status")
print(result.ok)
Same API as run() — just await it.
Result Object
| Property | Type | Description |
|---|---|---|
.stdout |
str |
Captured stdout |
.stderr |
str |
Captured stderr |
.code |
int |
Exit code |
.ok |
bool |
True if exit code is 0 |
.lines |
list[str] |
Stdout split into lines |
.json() |
Any |
Parse stdout as JSON |
str(r) |
str |
Returns stdout stripped |
bool(r) |
bool |
Returns .ok |
r | "cmd" |
Result |
Pipe stdout to next command |
run() Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
command |
str | list |
required | Command to run |
check |
bool |
True |
Raise ZapError on non-zero exit |
timeout |
float |
None |
Timeout in seconds |
cwd |
str |
None |
Working directory |
env |
dict |
None |
Extra env vars (merged with os.environ) |
stdin |
str |
None |
String to feed as stdin |
live |
bool |
False |
Stream output in real-time |
retries |
int |
0 |
Number of retry attempts on failure |
delay |
float |
1.0 |
Seconds between retries |
How It Works
- String command → runs with
shell=True(convenience for scripting) - List command → runs with
shell=False(safe, no shell injection) - check=True by default → raises
ZapErroron non-zero exit - live=True → uses
Popenwithselectorsfor real-time streaming - retries → catches failures and retries with configurable delay
- Zero dependencies → pure Python stdlib
- ~300 lines → read the whole source in 5 minutes
Support
If Zap saves you time, consider buying me a coffee.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
zap_sh-0.2.0.tar.gz
(10.8 kB
view details)
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zap_sh-0.2.0.tar.gz.
File metadata
- Download URL: zap_sh-0.2.0.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/5.1.1 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81644762b4a99d401448cb619628a6a05ad0ef9fab7186ca5ea97f3a147e7a33
|
|
| MD5 |
9e5c2998feec48471a56e28a51cff91c
|
|
| BLAKE2b-256 |
8bed8e05040859c7a9ccfc3b53b4c013c50605bac53511a577a793f24e7c1dbc
|
File details
Details for the file zap_sh-0.2.0-py3-none-any.whl.
File metadata
- Download URL: zap_sh-0.2.0-py3-none-any.whl
- Upload date:
- Size: 7.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/5.1.1 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23b0bbc6de7676f8515c1943334dc3366adcccde063a30709ee77589c08410b1
|
|
| MD5 |
e9b6b53739997af2ded0e3ce7798b17d
|
|
| BLAKE2b-256 |
c899ecb0b2f6849ef5df3c82f6986e489382f364da387e54c4b797eadae276be
|