Skip to main content

A Bash beautifier for the masses.

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

beautysh-6.4.2.tar.gz (73.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

beautysh-6.4.2-py3-none-any.whl (26.4 kB view details)

Uploaded Python 3

File details

Details for the file beautysh-6.4.2.tar.gz.

File metadata

  • Download URL: beautysh-6.4.2.tar.gz
  • Upload date:
  • Size: 73.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for beautysh-6.4.2.tar.gz
Algorithm Hash digest
SHA256 e3d762006e8930e6fec8842adeb7e124c283bd63dbd83a1466f91e4caf5765b4
MD5 6922ec64e3812dd2021ff68ffa6730b9
BLAKE2b-256 4988ce91f2dfb01112a39b2f36ae4296dcb49056cf2a3d0d39bd510bda3e4815

See more details on using hashes here.

Provenance

The following attestation bundles were made for beautysh-6.4.2.tar.gz:

Publisher: publish-pypi.yaml on lovesegfault/beautysh

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file beautysh-6.4.2-py3-none-any.whl.

File metadata

  • Download URL: beautysh-6.4.2-py3-none-any.whl
  • Upload date:
  • Size: 26.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for beautysh-6.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 42bb7dc79d55fbb4734fb7d91c61b52da06abd11929d44a1616e4702335eaff5
MD5 0da2c5837abc68ed89bb64b76f69f4c0
BLAKE2b-256 25cb1e7140c5207dba33e63e8bbcb5d201d918c5201ffb0e6b3be16813887013

See more details on using hashes here.

Provenance

The following attestation bundles were made for beautysh-6.4.2-py3-none-any.whl:

Publisher: publish-pypi.yaml on lovesegfault/beautysh

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page