CLI tool
lovesegfault/beautysh avatar
lovesegfault/beautysh

beautysh: a Bash beautifier that admits Bash is not C

A Bash beautifier for the masses.

538 stars47 forksPythonMIT

At a glance

What is it?
beautysh is a Python program that reformats Bash scripts, with configuration from command line flags, pyproject.toml and EditorConfig, function and variable style options, here-doc preservation and formatter off comments. It deliberately does not reimplement the Bash interpreter, so it handles the vast majority of scripts but not every edge case, and it overwrites files in place by default.
Who is it for?
Adopt beautysh if you maintain a repository of shell scripts and want a consistent style enforced across them, and you can run it in check mode first. Skip it if you need a guarantee that every construct is handled, since the tool is explicit that it does not try to reimplement the Bash interpreter.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is that Bash is not a language with a grammar

beautysh describes itself as a Bash beautifier for the masses, and the opening of the README explains why the problem is harder than it looks. Processing Bash scripts is not trivial, they are not like C or Java programs, because they have a lot of ambiguous syntax, and you can use keywords as variables. The example given is a single line of script.

shell
done=0;while (( $done <= 10 ));do echo done=$done;done=$((done+1));done

The same name carries three distinct meanings, and the README's conclusion from that is the design constraint of the whole project. The Bash interpreter can sort out this perversity, but the author decided not to try to recreate the Bash interpreter to beautify a script, which means there will be some border cases this Python program will not be able to process. The trade is stated with a number, that in tests with large Linux system Bash scripts, the error-free score was around 99 percent. That is an honest framing for a formatter, since a formatter that refuses to process a file is a different failure mode from one that produces wrong output, and a tool that gives up rather than guessing is the safer of the two. The project is MIT licensed, its primary language is Python, and the release history is regular, with v6.4.3 from 2026-03-12, v6.4.2 from 2025-11-18 and v6.4.1 from 2025-10-31, with the last push to the repository on 2026-10-19.

Three install routes, and the CLI entry point

The installation section offers three routes, and the Nix one is marked as recommended for development. The pip route is the shortest.

shell
pip install beautysh

The Nix route runs the tool directly from the repository without a permanent install.

shell
nix run github:lovesegfault/beautysh -- --help

Or the flake can be added to a project as an input, in the nix configuration form.

nix
{
  inputs.beautysh.url = "github:lovesegfault/beautysh";
  # ...
}

And the source route uses uv to sync the environment.

shell
git clone https://github.com/lovesegfault/beautysh
cd beautysh
uv sync

In use, the tool takes file names as arguments and beautifies each one in place, so the everyday invocation is a glob or a list of paths.

shell
beautysh file1.sh file2.sh file3.sh

The console script is registered in the package metadata as beautysh pointing at the module main, and the runtime dependencies are small, with colorama for output, editorconfig for reading configuration files and tomli for Python versions below 3.11 where the standard library has no TOML parser. The project requires Python 3.9 or newer, is classified as production and stable, and the package description is deliberately short, a Bash beautifier for the masses.

Configuration comes from three places, in a defined order

Beautysh supports multiple configuration sources with a stated priority, from highest to lowest: command line arguments, then the pyproject.toml file in a tool.beautysh section, then EditorConfig files, which have the lowest priority.

toml
[tool.beautysh]
indent_size = 4
tab = false
backup = false
check = false
force_function_style = "fnpar"  # Options: fnpar, fnonly, paronly
variable_style = "braces"  # Options: braces

The EditorConfig route only exposes two properties, since the whole point of respecting EditorConfig is that other tools already use it. The indent_style property, space or tab, maps to the tab flag, and the indent_size property maps to the indent size option.

ini
[*.sh]
indent_style = space  # or "tab"
indent_size = 4

The design here is worth pausing on, because it is the reason the tool fits into an existing repository without a fight. A formatter that insists on its own configuration file will be overridden by a project that already has conventions, whereas one that reads EditorConfig at the lowest priority and lets a project opt out of the defaults with a pyproject section is a tool a team can actually agree to run. The precedence order also means the flags always win, so a one-off run with different settings never requires editing a file.

Function and variable styles are the parts people disagree about

The command line options are listed in full, and two of them carry the real policy decisions. The force function style option, with the short form -s, forces a specific Bash function formatting, and there are three styles. The fnpar style is the function keyword with open and closed parentheses, giving function foo(). The fnonly style is the function keyword with no parentheses, giving function foo. The paronly style drops the function keyword and keeps the parentheses, giving foo(). The variable style option, on the other hand, has a single value, braces, which transforms $VAR into ${VAR} for consistency, so $HOME becomes ${HOME}, with the note that special variables such as $? and $1, and parameter expansions of the form ${VAR:-default}, are left unchanged. That carve-out is a sign of care, because rewriting a parameter expansion would change behaviour, whereas rewriting a plain variable reference is cosmetic. The other options are the practical ones, the indent size with the short form -i, a backup flag that writes a copy in the same path as the original, a check flag that only checks the files without doing any in-place beautify, a tab flag, and the version and help flags. The check mode and the backup flag together are the combination to use on a repository you care about.

Here-docs and formatter off comments, and the warning about binary content

Two behaviours are singled out in the README because they are where a formatter most easily breaks a script. The first is here-docs, which are handled with care, and there are probably some border cases it does not handle, with the basic idea given plainly: the originator knew what format he wanted in the here-doc, and a beautifier should not try to outguess him, so beautysh does all it can to pass the here-doc content along unchanged. The second is the pair of special comments, @formatter:off and @formatter:on, which are available to disable formatting around a block of statements, and this takes inspiration from the Eclipse feature. The example of the block is a command with backslash continuations, and the effect is that the odd indentation inside the block is left alone.

shell
# @formatter:off
command \
    --option1 \
        --option2 \
            --option3 \
# @formatter:on

The caution is the most important paragraph in the README and it deserves to be read in full. Because beautysh overwrites all the files submitted to it, this could have disastrous consequences if the files include some of the increasingly common Bash scripts that have appended binary content, a regime where beautysh has undefined behaviour, so please back up your files and do not treat beautysh as a harmless utility, even if that is true most of the time. That is an unusual admission, and it is a real one, since a script with a binary trailer is not a hypothetical in the wild and a formatter that rewrites such a file can corrupt it. The combination of the check flag, the backup flag and a version control working tree is the mitigation, and a formatter that silently corrupts a file that a shell later executes is a supply chain problem rather than a style problem.

Using it as a library, and the module layout

Beautysh can also be called as a module, which is the route to use if you want to format inside another tool or a test.

python3
from beautysh import BashFormatter

source = "if true;then\necho x\nfi"

formatter = BashFormatter(indent_size=4)
result = formatter.beautify_string(source)

if result.error is None:
    print(result.output)
else:
    print(f"error: {result.error}")

The return value is a FormatResult with an output string and an optional error string, and the README makes a point about it that matters for anyone using this as a library: the output is best-effort even when the error is set. So a caller cannot treat a non-empty error as meaning the output is unusable, and the practical advice is to check the error and treat the output as advisory. The module layout follows the same separation. There is a parser.py for Bash syntax parsing and analysis, a formatter.py for the core formatting logic including indentation calculation, a transformers.py for the style transformations covering function and variable styles, a config.py for configuration loading from pyproject.toml and EditorConfig, and a cli.py for the command line entry point. The testing story is visible in the dev dependencies, with hypothesis for property based testing, pytest with xdist for parallel runs, mypy and ruff, and vermin for checking the minimum supported Python version. The pytest configuration runs with strict markers and strict config, and coverage is configured with branch coverage on the beautysh package.

Editorial conclusion

Adopt beautysh if you maintain a repository of shell scripts and want a consistent style enforced across them, and you can run it in check mode first. Skip it if you need a guarantee that every construct is handled, since the tool is explicit that it does not try to reimplement the Bash interpreter. Verify first that the scripts you plan to run it over contain no appended binary content, since the README warns that the behaviour is undefined in that case, and that your chosen function and variable styles match the convention already in the repository.

Frequently asked questions

What does beautysh do and what Bash scripts does it handle?

It reformats Bash scripts, and the README is explicit that it does not reimplement the Bash interpreter, so some border cases cannot be processed. The author reports an error-free score of around 99 percent in tests with large Linux system Bash scripts. The primary language is Python, the project is MIT licensed, and the package requires Python 3.9 or newer.

How do I configure beautysh?

There are three configuration sources with a defined priority, command line arguments first, then the tool.beautysh section of pyproject.toml, then EditorConfig files at the lowest priority. From EditorConfig only indent_style and indent_size are read, mapping to the tab flag and the indent size option. The command line also offers function styles of fnpar, fnonly and paronly, and a variable style of braces, which rewrites $VAR as ${VAR} while leaving special variables and parameter expansions unchanged.

Is it safe to run beautysh on my shell scripts?

The README warns that it overwrites all files submitted to it, and that scripts with appended binary content fall into a regime where its behaviour is undefined. The safe approach is to run the --check flag first, keep the --backup option enabled, and use version control, and the documentation asks you not to treat beautysh as a harmless utility.

Official sources

  1. License: MIT
  2. lovesegfault/beautysh on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lovesegfault-beautysh.svg)](https://hysenlabs.com/projects/lovesegfault-beautysh)

Was this useful?