Skip to main content

Bash_PurePython

PurePython implementations of common bash commands, for use with pyodide.

These are mostly vibe coded, use at your own risk.

Running scripts

bash_purepython.execute_command runs bash-like scripts on asyncio, with no subprocesses or threads, which is what pyodide needs. A script is split into top-level blocks, each block is turned into a plan, the plan is executed, and any script text found along the way (loop bodies, function bodies, $( ) substitutions, subshells) re-enters the same loop.

Under pyodide, where an event loop is already running, await the entry point (for example from runPythonAsync with top-level await):

from bash_purepython.execute_command import run_script_async
from bash_purepython.shell_state import ShellState

state = ShellState.for_terminal()
exit_code = await run_script_async("yes | head -n 3; echo $(echo sub) | cat", state)
print(state.stdout, state.stderr, exit_code)

On CPython, run_script(script, state) drives its own loop, and ShellRunner keeps one loop alive across calls so background jobs survive between them.

Pipelines stream lazily: a command declares the kinds of input and output it supports (Iterator[str], str, Iterator[bytes], bytes, synchronous or asynchronous) and the engine negotiates a shared kind between neighbouring stages, so yes | head runs yes only as far as head reads. A group, loop, conditional or function used as a pipeline stage runs as its own task and streams through a bounded queue, so ( yes ) | head -n 2 terminates too. Standard output and standard error are written to the state's sinks as each chunk is produced and are also recorded on the state. A sink may be an async callable, such as a JavaScript function, and is awaited per chunk.

cmd & starts a background job on the event loop. $! holds its pid, the fake ps lists the job table (R running, D done, K killed), kill PID cancels a job, wait [PID] waits and reaps, and jobs lists them bash-style.

Register extra commands by subclassing bash_purepython.command.command.Command and adding an instance to a CommandRegistry. A command's run may be a plain function or a coroutine, so it can await a browser promise such as fetch.

Supported so far: pipelines (|, |&, !), &&/|| lists, if/elif/else, for, while, until, functions, subshells, brace groups, background jobs, redirections on commands and on compound commands (>, >>, <, 2>, 2>>, 2>&1, &>, heredocs, here-strings, /dev/null), parameter and command substitution, arithmetic ($(( )) and the (( )) command), the [[ ]] conditional command, pathname expansion (*, ?, [...]), and the builtins cd, export, unset, exit, return, break, continue, wait, jobs. Script files run with bash file, sh file, bash -c text, a script on standard input, or by a path such as ./file, each against a copy of the state; source file and . file run one against the live state. Commands shipped: [, basename, cat, cp, cut, date, dirname, echo, env, false, find, grep, gunzip, gzip, head, help, kill, ls, mkdir, mv, nl, printf, ps, pwd, realpath, rev, rm, rmdir, seq, sleep, test, true, yes. Every command takes --help; -h is left free for the commands that use it, such as ls -h and grep -h.

Known limitations: case, select and process substitution are not implemented; a $( ) substitution that never stops writing is cut off at the executor's output limit.

Example terminal

examples/terminal.py is a small interactive terminal built on the engine, showing what a host supplies: one ShellState for the session, one ShellRunner so background jobs survive between lines, a prompt, and a continuation prompt while a quote or here-document is open. Run it with:

uv run python -m examples.terminal

For a browser host, bash_purepython.pyodide_session.PyodideSession plays the same role under Pyodide and adds commands the page implements itself.

Testing

uv run pytest runs the in-memory suite. tests/test_live_bash.py runs the same several hundred scripts through a real bash binary and the engine and compares standard output and exit codes; it is marked live_bash_test and excluded by default. Run it with:

uv run pytest -m live_bash_test

Metadata

Release files for bash-purepython 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bash-purepython 0.4.0
File Size Uploaded
bash_purepython-0.4.0.tar.gz 114.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bash-purepython 0.4.0
File Interpreter ABI Platform
bash_purepython-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 220.9 kB

Release files / bash_purepython-0.4.0.tar.gz

Download URL bash_purepython-0.4.0.tar.gz
Size 114.1 kB
Tags Source
SHA-256 checksum
How to use checksums
421d865ca53b67c993c5da0d8299bbb2c35e82de48563340c63bb2d23baff4a5
BLAKE2b-256 checksum
How to use checksums
0a614e62bfdacfd24259a2a38f28697fb7e55a1ea24bf5203f69989e419ed9fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / bash_purepython-0.4.0-py3-none-any.whl

Download URL bash_purepython-0.4.0-py3-none-any.whl
Size 106.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a9b3186a79529bf3636fb35ff94345dc41c6428d0047c2d00ed3f50531ae5a38
BLAKE2b-256 checksum
How to use checksums
15937263344348ba5f5a89e7a5c82423cc07bff1e4e94232ff3dfcf2cbe84c0d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.0

2 release files

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page