Skip to main content

trapdoor

An interactive step-debugger for Bash scripts — built entirely out of things bash already ships.

No patched bash. No ptrace. No set -x archaeology. The debugger side of trapdoor is ~60 lines of pure bash injected through BASH_ENV; it talks to a Rust controller over /dev/tcp, bash's built-in TCP socket. The DEBUG trap does the rest: every command in your script pauses and asks permission before it runs.

Born from a wish on Ask HN: What developer tool do you wish existed in 2026?:

"No debugging interface for shell scripts — stop at a specific point in the script, modify any commands and execute the step."

So: breakpoints (conditional ones too), step / next / finish, backtraces, source listings — and a REPL that evaluates inside the live script, so assignments stick. Change a variable mid-loop and watch the script take the other branch.

Demo

$ trapdoor -b 'demo.sh:13 if (( count == 1 ))' -r examples/demo.sh
breakpoint #1 at demo.sh:13  if (( count == 1 ))
hello, apple

examples/demo.sh:13 [depth 1] ● breakpoint #1
  → count=$((count + 1))
(tdb) p count fruit
declare -- count="1"
declare -- fruit="banana"
(tdb) !count=40
(tdb) c
hello, banana
hello, cherry
processed 43 fruits, total=602

That 43 is not a typo — !count=40 rewrote the loop counter in the running script.

New here? The 15-minute tutorial walks from first step to patching a live script's variables mid-run.

How it works

┌─────────────────────┐   TCP 127.0.0.1:<random>   ┌──────────────────────────┐
│ trapdoor (Rust)     │◄───────────────────────────►│ bash your-script.sh      │
│  breakpoints, REPL, │   STOP file:line:cmd  ───►  │  stub via BASH_ENV:      │
│  step logic, source │   ◄─── GO / EVAL / BT       │   exec {fd}<>/dev/tcp/…  │
│  listings, colors   │                             │   trap '…' DEBUG         │
└─────────────────────┘                             └──────────────────────────┘
  1. The controller binds a random localhost port and launches your script with BASH_ENV pointing at a tiny stub (src/stub.sh).
  2. The stub opens a socket with exec {fd}<>/dev/tcp/127.0.0.1/$PORT — no nc, no socat, no python; /dev/tcp is interpreted by bash itself.
  3. It arms the DEBUG trap (with set -o functrace so functions inherit it). Before every simple command, the stub reports file:line, call depth and the command text, then blocks until the controller answers.
  4. GO runs the command. EVAL <code> runs arbitrary bash in the script's own execution context — that's how p, x, ! and conditional breakpoints work. BT walks FUNCNAME/BASH_SOURCE/BASH_LINENO for a backtrace.
  5. If the controller dies, the stub disarms the trap and the script runs free. No zombie hostages.

Install

cargo install --path .          # or: cargo build --release

One static-ish binary, zero crate dependencies (std only). Works anywhere bash is compiled with /dev/tcp support — Linux distros, macOS, and Git Bash / MSYS2 on Windows all qualify.

Usage

trapdoor [OPTIONS] <script.sh> [script args...]

-b, --break <SPEC>   breakpoint: <line> | <file>:<line> | <file>:<line> if <bash-cond>
-r, --run            don't stop at the first command; run until a breakpoint
    --bash <PATH>    which bash to use
    --no-color       disable ANSI colors

At the (tdb) prompt:

command effect
s / step stop at the next command, anywhere (steps into functions)
n / next next command at this depth or shallower (steps over calls)
f / finish run until the current function returns
c / continue run until a breakpoint
u <line> run until that line in the current file (one-shot)
enter repeat the last motion command
b 13 · b utils.sh:40 breakpoint
b 13 if (( count == 2 )) conditional breakpoint — the condition is raw bash run in the script: (( … )), [[ … ]], even grep -q …
bl / d <id> list / delete breakpoints
w <bash-expr> watch: evaluated in the script and shown at every stop (w $count)
wl / wd <id> list / delete watches
p var… declare -p variables (arrays and maps print properly)
x code / !code run bash in the live script — assignments stick
bt backtrace
l [line] source listing around the current (or given) line
q kill the script and quit

Honest caveats

  • Commands inside $( … ) command substitutions and pipeline segments run in subshells; trapdoor deliberately stays quiet there (two writers on one socket would corrupt the protocol). You still stop on the enclosing command.
  • The DEBUG trap fires per simple command, so a compound like for … stops once at the loop head, then at each body command — same as bash -x granularity.
  • Scripts that read stdin share it with the debugger REPL. Redirect one of them.
  • Requires bash ≥ 4.1 (for {fd}<> auto-allocation) built with /dev/tcp.

Why not bashdb?

bashdb is venerable and more featureful, but it's a 1MB bash-in-bash interpreter you have to install on the target machine. trapdoor's target-side footprint is one temp file and one socket, injected by the environment — nothing to install where the script runs, and the brains stay in one fast binary.

License

MIT

Metadata

Release files for trapdoor-sh 0.1.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 trapdoor-sh 0.1.0
File Size Uploaded
trapdoor_sh-0.1.0.tar.gz 16.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for trapdoor-sh 0.1.0
File Interpreter ABI Platform
trapdoor_sh-0.1.0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
trapdoor_sh-0.1.0-py3-none-manylinux_2_39_x86_64.whl Python 3 none Linux glibc 2.39+ x86-64 Details
trapdoor_sh-0.1.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details

Total release size: 627.2 kB

Release files / trapdoor_sh-0.1.0.tar.gz

Download URL trapdoor_sh-0.1.0.tar.gz
Size 16.3 kB
Tags Source
SHA-256 checksum
How to use checksums
36fddfd3b76b0e240adee1f856a3e132b40e9205527628a95947ef5e63c19155
BLAKE2b-256 checksum
How to use checksums
2abaf87d8e0954102cbc4a3de3a951b33f86651695c89687b44676968e29e9f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 28, 2026.

Transparency log

Release files / trapdoor_sh-0.1.0-py3-none-win_amd64.whl

Download URL trapdoor_sh-0.1.0-py3-none-win_amd64.whl
Size 164.5 kB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
032872c2cb9db6e4255cae1773970fab97a8d9802cc5a0f750409ba772c5ee30
BLAKE2b-256 checksum
How to use checksums
2c80feba85082459dbd14d09ff2c3112b511f77c3b98194da1bfa45eb758783d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 28, 2026.

Transparency log

Release files / trapdoor_sh-0.1.0-py3-none-manylinux_2_39_x86_64.whl

Download URL trapdoor_sh-0.1.0-py3-none-manylinux_2_39_x86_64.whl
Size 235.4 kB
Tags Linux glibc 2.39+ x86-64 Python 3
SHA-256 checksum
How to use checksums
8a335cd92e821a51932ea72d7050b7894a8165bb6521e9905c19f21ec535007d
BLAKE2b-256 checksum
How to use checksums
33de78ed3f411e0c778b4e29b2e162b286e06d9fe446d39095110bcf50548ca7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 28, 2026.

Transparency log

Release files / trapdoor_sh-0.1.0-py3-none-macosx_11_0_arm64.whl

Download URL trapdoor_sh-0.1.0-py3-none-macosx_11_0_arm64.whl
Size 211.1 kB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
847a02cc50e0958c314dbf8687d6f2d96da3c74e1e5db5d4255981ff2b2cc9b9
BLAKE2b-256 checksum
How to use checksums
34b1313806713d157ea64e30d503b2cfa8afd750b63ee89efaf263376f74b128
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 28, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

4 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