Async shell command library with operator-based DSL
Project description
shish
Async shell commands for Python with operator-based piping.
from shish import sh
await (sh.cat("input.txt") | sh.grep("error") | sh.wc("-l"))
Python operators map directly to shell:
# Piping
await (sh.cat("file") | sh.grep("pattern") | sh.wc("-l"))
# Redirections
await (sh.curl("http://example.com") > "page.html") # stdout to file
await (sh.echo("line") >> "log.txt") # append
await (sh.grep("error") < "input.txt") # stdin from file
await (sh.grep("error") << "line1\nline2\n") # stdin from string
# Process substitution
await sh.diff(from_proc(sh.sort("a.txt")), from_proc(sh.sort("b.txt")))
# Kwargs to flags
await sh.git.commit(message="fix bug", amend=True)
# -> git commit --message 'fix bug' --amend
# Capture output (returns str, decoded as utf-8)
stdout = await out(sh.ls("-la"))
stdout = await out(sh.cmd(), encoding=None) # raw bytes
Why Not stdlib?
Subprocess calls are verbose and error-prone:
import subprocess
# Quoting? Escaping? Shell injection?
subprocess.run("cat input.txt | grep error | wc -l", shell=True)
# Safe, but unwieldy, no way to pipe
subprocess.run(["git", "commit", "--message", "fix bug", "--amend"])
The async version is worse - correct concurrent piping requires manual fd wiring:
import asyncio
async def pipeline():
cat = await asyncio.create_subprocess_exec(
"cat", "input.txt",
stdout=asyncio.subprocess.PIPE
)
grep = await asyncio.create_subprocess_exec(
"grep", "error",
stdin=cat.stdout,
stdout=asyncio.subprocess.PIPE
)
wc = await asyncio.create_subprocess_exec(
"wc", "-l",
stdin=grep.stdout
)
await asyncio.gather(cat.wait(), grep.wait(), wc.wait())
Features
Concurrent pipelines - All stages run in parallel via os.pipe(), just like a real shell. No buffering entire outputs in memory.
Async-native - Commands are lazy until awaited. Build pipelines, pass them around, execute when ready.
Pipefail by default - Returns first non-zero exit code from any pipeline stage.
SIGPIPE handling - Early termination works naturally (128 + signal for killed processes).
Control Flow
Use Python:
# Sequential (&&)
if await sh.mkdir("dir") == 0:
await sh.touch("dir/file")
# Fallback (||)
if await sh.test("-f", "config.json") != 0:
await sh.cp("config.default.json", "config.json")
# Timeout
await asyncio.wait_for(sh.long_running(), timeout=30)
# Background
task = asyncio.create_task(sh.server())
Combinators
Operators delegate to functions for when you need them:
from shish import out, pipe, write, append, read, input_, from_proc, to_proc
pipe(sh.a(), sh.b(), sh.c()) # varargs pipeline
write(read(sh.cat(), "in"), "out") # functional composition
stdout = await out(sh.ls()) # capture stdout as str
See Also
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
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 shish-0.0.1.tar.gz.
File metadata
- Download URL: shish-0.0.1.tar.gz
- Upload date:
- Size: 8.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99eab772f6a97c53193fadc2e413b62cabf4b4352fc87fb89a77716d278aaee4
|
|
| MD5 |
e34bfee9b5d159927be444f35ffeadc7
|
|
| BLAKE2b-256 |
2cd5ac90478a51743812935a86874c966348d6b996feba95bebc133bfeb4a20b
|
File details
Details for the file shish-0.0.1-py3-none-any.whl.
File metadata
- Download URL: shish-0.0.1-py3-none-any.whl
- Upload date:
- Size: 9.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
727d83fd39aabe9b0b101bce124332253ed1653f4c2f27be06eb0552f9c67b37
|
|
| MD5 |
0dbc79e96e746f3f91111d05a17ff48d
|
|
| BLAKE2b-256 |
07d03018ca03808bdb6f86ba5d79390a7e8e05aee11975450950a5a634e4b337
|