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.3.tar.gz (75.7 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.3-py3-none-any.whl (27.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: beautysh-6.4.3.tar.gz
  • Upload date:
  • Size: 75.7 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.3.tar.gz
Algorithm Hash digest
SHA256 2aceb602fa7e27dafd24d5bc480986e17870873c2827a2b8d720118cafac3018
MD5 72969d730b3e0b9a6e4e731df633f934
BLAKE2b-256 ae9ded7b7dc146881698f5d71ce8a384f8e8f80790b1b12c73598efa81b8c3ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for beautysh-6.4.3.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.3-py3-none-any.whl.

File metadata

  • Download URL: beautysh-6.4.3-py3-none-any.whl
  • Upload date:
  • Size: 27.0 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 5b8fab21a2da6231d916489be74772615b33ce5d3e9dc736ebc1f953621b323c
MD5 b67fa951a460252c688d48d2f9a7ddb3
BLAKE2b-256 f7c93a4d1b3d91d49cc9acf0ad909ee91bd045062ea5866e1cd4e717d7769a2a

See more details on using hashes here.

Provenance

The following attestation bundles were made for beautysh-6.4.3-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