beautify_bash
A code formatter / beautifier for bash and zsh shell scripts.
Originally written in Ruby, then Python, by Paul Lutus; revived here with a proper package layout, a Typer CLI, uv tooling, a test suite, and support for shell dialects.
Install
uv tool install beautify-bash # as a standalone command
uv add beautify-bash # as a project dependency
pipx install beautify-bash # or with pipx
Run it without installing:
uvx beautify-bash script.sh
Requires Python 3.9 or newer (tested through 3.14).
Use
Like shfmt and gofmt, it prints to standard output and changes nothing on
disk unless you ask it to:
beautify-bash script.sh # print the formatted script
beautify-bash -w script.sh # rewrite in place, keeping script.sh~
beautify-bash -w --no-backup script.sh # rewrite without a backup
beautify-bash -o tidy.sh script.sh # write the result to another file
beautify-bash --check src/*.sh # exit 1 if anything would change (CI)
beautify-bash --diff script.sh # show a unified diff, write nothing
beautify-bash --indent 4 script.sh # four spaces per level
beautify-bash --tabs script.sh # indent with tabs
beautify-bash -d zsh script.zsh # force a dialect
cat script.sh | beautify-bash - # stdin to stdout
beautify-bash -w src/*.sh # format a whole directory
python -m beautify_bash script.sh # same thing via the module
Standard output carries only the formatted script; progress notes, diffs of
the --check kind, and errors go to standard error, so beautify-bash x.sh > y.sh is safe.
Options
| Option | Meaning |
|---|---|
| (none) | Print the formatted script to stdout; several inputs are concatenated. |
-w, --write |
Rewrite each input file in place. |
-o, --output PATH |
Write the result to PATH (- means stdout). |
-i, --indent N |
Spaces per indentation level (default 2). |
--tabs / --spaces |
Indent with tab characters instead of spaces. |
-d, --dialect |
auto (default), bash, or zsh. |
--check |
Write nothing; exit 1 if a file would change. |
--diff |
Print a unified diff instead of writing. |
--backup / --no-backup |
With -w, keep the original as FILE~ (default on). |
-q, --quiet |
Suppress per-file progress notes. |
-V, --version |
Print the version and exit. |
-w, -o, --check and --diff are mutually exclusive, and -w cannot
rewrite standard input.
Exit codes: 0 clean, 1 a file changed (under --check/--diff) or a
syntax problem was reported, 2 a file could not be read or written, or the
options conflict.
Dialects
--dialect auto picks the dialect from the shebang line first, then the file
name (.zsh, .zshrc, .zshenv, …), and falls back to bash.
| Dialect | Block keywords |
|---|---|
bash |
case/esac, if/then/elif/else/fi, do/done, {}, (), [] |
zsh |
everything bash has, plus foreach … end and } always { |
Adding a shell means adding one Dialect instance in
dialects.py — the indenter itself is dialect
agnostic.
Library use
from beautify_bash import ZSH, Beautifier, beautify_string
result = beautify_string(open("script.sh").read())
print(result.text)
print(result.dialect.name, result.ok, result.errors)
# Explicit configuration
tidy = Beautifier(indent_char="\t", indent_size=1, dialect=ZSH)
print(tidy.format("foreach f (a b)\nprint $f\nend").text)
The 1.x API (BeautifyBash, beautify_string(data, path) -> (text, error),
beautify_file, tab_str, tab_size) still works.
What is preserved verbatim
Re-indenting must never change what a script does, so these regions are passed through byte for byte:
- here-document bodies (
<<EOF, and<<-EOFwith its indented terminator), - lines inside a multi-line
'…'or"…"string, - everything inside comments and quotes is ignored for keyword counting.
Shell reserved words only count as keywords in command position — at the
start of a line or after ;, &, |, or a bracket — so echo done no longer
dedents the following lines.
Pre-commit
repos:
- repo: local
hooks:
- id: beautify-bash
name: beautify-bash
entry: beautify-bash -w
language: system
types: [shell]
Development
uv sync # create .venv and install dev dependencies
uv run pytest # run the tests
uv run pytest --cov # with coverage
uv run ruff check . # lint
uv run ruff format . # format the Python sources
uv run mypy # type check
The test suite is in tests/:
- test_beautifier.py — the indenting core,
- test_dialects.py — dialect detection and zsh syntax,
- test_cli.py — the CLI, via Typer's
CliRunner, - test_mocks.py — worked
unittest.mockexamples (patching methods,mock_open, fake streams, spies, stubs, autospec).
Roadmap
See TODO.md.
License
GPL-2.0-or-later. See LICENSE.
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 beautify_bash-2.0.0.tar.gz.
File metadata
- Download URL: beautify_bash-2.0.0.tar.gz
- Upload date:
- Size: 92.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9eaef33f8c7a967fc52ce90aaf235b84b5df3049db579c64ef3f0ffe670d0a9
|
|
| MD5 |
f889779193f2bfe8d150f577764748fb
|
|
| BLAKE2b-256 |
e4e5caef231235646d08a4d47622ba7d73b1736247bc250fbca4dcbc1e0c1d61
|
File details
Details for the file beautify_bash-2.0.0-py3-none-any.whl.
File metadata
- Download URL: beautify_bash-2.0.0-py3-none-any.whl
- Upload date:
- Size: 20.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db64a72d645226a220cfd84b52f6baa1bd4fb63f00af7c04d4822f46e8b3d385
|
|
| MD5 |
6f5a7951dd2e1b3ad0d4126a10317ea4
|
|
| BLAKE2b-256 |
d9dec7ff8b3977481bd7b7cd656b24856a54162fa772f7dcb26aa537c63c8f58
|