Skip to main content

beautysh CI

This program takes upon itself the hard task of beautifying Bash scripts (yeesh). Processing Bash scripts is not trivial, they aren't like C or Java programs — they have a lot of ambiguous syntax, and (shudder) you can use keywords as variables. Years ago, while testing the first version of this program, I encountered this example:

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

Same name, but three distinct meanings (sigh). The Bash interpreter can sort out this perversity, but I decided not to try to recreate the Bash interpreter to beautify a script. This means there will be some border cases this Python program won't be able to process. But in tests with large Linux system Bash scripts, its error-free score was ~99%.

Installation

Using pip

pip install beautysh

Using Nix (recommended for development)

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

Or add to your flake.nix:

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

From source with uv

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

Usage

You can call Beautysh from the command line such as

beautysh file1.sh file2.sh file3.sh

in which case it will beautify each one of the files.

Configuration

Beautysh supports multiple configuration sources with the following priority (highest to lowest):

  1. Command-line arguments (highest priority)
  2. pyproject.toml - [tool.beautysh] section
  3. EditorConfig - .editorconfig files (lowest priority)

pyproject.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

EditorConfig

Beautysh respects EditorConfig settings:

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

Supported EditorConfig properties:

  • indent_style: Maps to --tab flag (space/tab)
  • indent_size: Maps to --indent-size option

Command-Line Options

Available flags are:

  --indent-size INDENT_SIZE, -i INDENT_SIZE
                        Sets the number of spaces to be used in indentation.
  --backup, -b          Beautysh will create a backup file in the same path as
                        the original.
  --check, -c           Beautysh will just check the files without doing any
                        in-place beautify.
  --tab, -t             Sets indentation to tabs instead of spaces.
  --force-function-style FORCE_FUNCTION_STYLE, -s FORCE_FUNCTION_STYLE
                        Force a specific Bash function formatting. See below
                        for more info.
  --variable-style VARIABLE_STYLE
                        Force a specific variable style. See below for options.
  --version, -v         Prints the version and exits.
  --help, -h            Print this help message.

Bash function styles that can be specified via --force-function-style are:
  fnpar: function keyword, open/closed parentheses, e.g.      function foo()
  fnonly: function keyword, no open/closed parentheses, e.g.  function foo
  paronly: no function keyword, open/closed parentheses, e.g. foo()

Variable styles that can be specified via --variable-style are:
  braces: transform $VAR to ${VAR} for consistency (e.g., $HOME becomes ${HOME})
          Note: Special variables ($?, $1, etc.) and parameter expansions
          (${VAR:-default}) are left unchanged.

You can also call beautysh as a module:

from beautysh import BashFormatter

source = "my_string"

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

For more control, you can use individual components:

from beautysh import BashParser, StyleTransformer, BashFormatter

# Parse and analyze Bash syntax
parser = BashParser()
test_record = parser.get_test_record('if [ "$x" = "y" ]; then')

# Transform styles
transformer = StyleTransformer()
transformed = transformer.apply_variable_style('echo $HOME', 'braces')  # -> 'echo ${HOME}'

# Format complete scripts
formatter = BashFormatter(indent_size=2, variable_style='braces')
formatted, error = formatter.beautify_string(script)

As written, beautysh can beautify large numbers of Bash scripts when called from a variety of means,including a Bash script:

#!/bin/sh

for path in `find /path -name '*.sh'`
do
   beautysh $path
done

As well as the more obvious example:

$ beautysh *.sh

CAUTION: 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 don't treat Beautysh as a harmless utility. Even if that is true most of the time.

Beautysh handles Bash here-docs with care(and there are probably some border cases it doesn't handle). The basic idea is that the originator knew what format he wanted in the here-doc, and a beautifier shouldn't try to outguess him. So Beautysh does all it can to pass along the here-doc content unchanged:

if true
then

   echo "Before here-doc"

   # Insert 2 lines in file, then save.
   #--------Begin here document-----------#
vi $TARGETFILE <<x23LimitStringx23
i
This is line 1 of the example file.
This is line 2 of the example file.
^[
ZZ
x23LimitStringx23
   #----------End here document-----------#

   echo "After here-doc"

fi

Special comments @formatter:off and @formatter:on are available to disable formatting around a block of statements.

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

This takes inspiration from the Eclipse feature.

Development

Using Nix (recommended)

The easiest way to start developing is with Nix:

# Enter development shell with all dependencies
nix develop

# Run tests
pytest tests/

# Run type checker
mypy beautysh/

# Run linter and formatter
ruff check beautysh/ tests/
ruff format beautysh/ tests/

# Format all code (Nix, YAML, Markdown, Python)
nix fmt

# Run all pre-commit checks
pre-commit run --all-files

The development shell provides:

  • Python 3.12 with all dependencies
  • Editable install (changes to code are immediately reflected)
  • All development tools (pytest, mypy, ruff, hypothesis)
  • Pre-commit hooks automatically installed

Architecture

Beautysh has a modular architecture for maintainability:

  • beautysh/parser.py - Bash syntax parsing and analysis
  • beautysh/formatter.py - Core formatting logic with indentation calculation
  • beautysh/transformers.py - Style transformations (function/variable styles)
  • beautysh/config.py - Configuration loading (pyproject.toml, EditorConfig)
  • beautysh/cli.py - Command-line interface
  • beautysh/diff.py - Diff output for check mode
  • beautysh/types.py - Type definitions and dataclasses
  • beautysh/constants.py - Pre-compiled regex patterns and constants

Performance Tools

The tools/ directory contains performance analysis scripts:

# Benchmark formatter performance
python tools/benchmark.py

# Profile with cProfile to identify hotspots
python tools/profile_formatter.py

See tools/README.md for details.

Using uv

# Install dependencies
uv sync

# Activate virtual environment and run tests
uv run pytest tests/

Contributing

Contributions are welcome and appreciated, however test cases must be added to prevent regression. Adding a test case is easy, and involves the following:

  1. Create a file tests/fixtures/my_test_name_raw.sh containing the unformatted version of your test case.
  2. Create a file tests/fixtures/my_test_name_formatted.sh containing the formatted version of your test case.
  3. Register your test case in tests/test_integration.py. It should look something like this:
def test_my_test_name(fixture_dir):
    assert_formatting(fixture_dir, "my_test_name")

Before submitting a PR, please ensure:

  • All tests pass: pytest tests/
  • Code is formatted and linted: ruff check --fix . && ruff format .
  • Type checking passes: mypy beautysh/

Or simply run all checks at once:

pre-commit run --all-files

This will run:

  • pytest (all 172 tests including property-based tests)
  • mypy (type checking)
  • ruff (linting and formatting)
  • treefmt (Nix, YAML, Markdown formatting)

Originally written by Paul Lutus

Release files for beautysh 6.4.3

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

Source distribution (sdist)

Source distribution for beautysh 6.4.3
File Size Uploaded
beautysh-6.4.3.tar.gz 75.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for beautysh 6.4.3
File Interpreter ABI Platform
beautysh-6.4.3-py3-none-any.whl Python 3 none any Details

Total release size: 102.8 kB

Release files / beautysh-6.4.3.tar.gz

Download URL beautysh-6.4.3.tar.gz
Size 75.7 kB
Tags Source
SHA-256 checksum
How to use checksums
2aceb602fa7e27dafd24d5bc480986e17870873c2827a2b8d720118cafac3018
BLAKE2b-256 checksum
How to use checksums
ae9ded7b7dc146881698f5d71ce8a384f8e8f80790b1b12c73598efa81b8c3ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 12, 2026.

Transparency log

Release files / beautysh-6.4.3-py3-none-any.whl

Download URL beautysh-6.4.3-py3-none-any.whl
Size 27.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5b8fab21a2da6231d916489be74772615b33ce5d3e9dc736ebc1f953621b323c
BLAKE2b-256 checksum
How to use checksums
f7c93a4d1b3d91d49cc9acf0ad909ee91bd045062ea5866e1cd4e717d7769a2a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

6.4.3 This release

2 release files

6.4.2

2 release files

6.4.1

2 release files

6.4.0

2 release files

6.3.3

2 release files

6.3.2

2 release files

6.3.1

2 release files

6.3.0

2 release files

6.2.1

2 release files

6.2.0

2 release files

6.1.0

2 release files

6.0.1

1 release file

6.0.0

1 release file

5.0.2

1 release file

5.0.1

1 release file

5.0.0

1 release file

4.1

3 release files

4.0

2 release files

3.12

1 release file

3.11

2 release files

3.10

2 release files

3.9

2 release files

3.8

1 release file

3.7

1 release file

3.6

1 release file

3.5

1 release file

3.4

1 release file

3.3

1 release file

3.2

1 release file

3.1

1 release file

3.0

1 release file

2.2.2

1 release file

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