Skip to main content

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

  • sh - Popular shell wrapper. Uses _in= for piping but processes run sequentially due to Python's eager evaluation. Sync-only.

  • plumbum - Supports | operator but requires cmd["arg1", "arg2"]() syntax. Has async support but more complex API.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shish-0.0.1.tar.gz (8.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shish-0.0.1-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

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

Hashes for shish-0.0.1.tar.gz
Algorithm Hash digest
SHA256 99eab772f6a97c53193fadc2e413b62cabf4b4352fc87fb89a77716d278aaee4
MD5 e34bfee9b5d159927be444f35ffeadc7
BLAKE2b-256 2cd5ac90478a51743812935a86874c966348d6b996feba95bebc133bfeb4a20b

See more details on using hashes here.

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

Hashes for shish-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 727d83fd39aabe9b0b101bce124332253ed1653f4c2f27be06eb0552f9c67b37
MD5 0dbc79e96e746f3f91111d05a17ff48d
BLAKE2b-256 07d03018ca03808bdb6f86ba5d79390a7e8e05aee11975450950a5a634e4b337

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page