# beautysh [](https://github.com/lovesegfault/beautysh/actions/workflows/ci.yaml)
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:
```shell
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
```shell
pip install beautysh
```
### Using Nix (recommended for development)
```shell
nix run github:lovesegfault/beautysh -- --help
```
Or add to your `flake.nix`:
```nix
{
inputs.beautysh.url = "github:lovesegfault/beautysh";
# ...
}
```
### From source with uv
```shell
git clone https://github.com/lovesegfault/beautysh
cd beautysh
uv sync
```
## Usage
You can call Beautysh from the command line such as
```shell
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)
1. **pyproject.toml** - `[tool.beautysh]` section
1. **EditorConfig** - `.editorconfig` files (lowest priority)
#### pyproject.toml
```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](https://editorconfig.org/) settings:
```ini
[*.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:
```python3
from beautysh import Beautify
source = "my_string"
result, error = Beautify().beautify_string(source)
```
As written, beautysh can beautify large numbers of Bash scripts when called
from a variety of means,including a Bash script:
```shell
#!/bin/sh
for path in `find /path -name '*.sh'`
do
beautysh $path
done
```
As well as the more obvious example:
```shell
$ 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:
```shell
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.
```shell
# @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:
```shell
# Enter development shell with all dependencies
nix develop
# Run tests
pytest tests/
# Run type checker
mypy .
# Run linter
flake8 .
# Format code
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, flake8, black, isort)
- Pre-commit hooks automatically installed
### Using uv
```shell
# 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.
1. Create a file `tests/fixtures/my_test_name_formatted.sh` containing the formatted version
of your test case.
1. Register your test case in `tests/test_integration.py`. It should look
something like this:
```python
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: `nix fmt` (or `black . && isort .`)
- Type checking passes: `mypy .`
- Linting passes: `flake8 .`
Or simply run all checks at once:
```shell
pre-commit run --all-files
```
______________________________________________________________________
Originally written by [Paul Lutus](http://arachnoid.com/python/beautify_bash_program.html)
Raw data
{
"_id": null,
"home_page": null,
"name": "beautysh",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "auto, bash, beautifier, beautify, script, shell",
"author": null,
"author_email": "Bernardo Meurer <bernardo@meurer.org>",
"download_url": "https://files.pythonhosted.org/packages/34/5a/4acfcfecca6fe9e1dc25f71f39fea011672569040e5f816e36f55f48e588/beautysh-6.3.1.tar.gz",
"platform": null,
"description": "# beautysh [](https://github.com/lovesegfault/beautysh/actions/workflows/ci.yaml)\n\nThis program takes upon itself the hard task of beautifying Bash scripts\n(yeesh). Processing Bash scripts is not trivial, they aren't like C or Java\nprograms \u2014 they have a lot of ambiguous syntax, and (shudder) you can use\nkeywords as variables. Years ago, while testing the first version of this\nprogram, I encountered this example:\n\n```shell\ndone=0;while (( $done <= 10 ));do echo done=$done;done=$((done+1));done\n```\n\nSame name, but three distinct meanings (sigh). The Bash interpreter can sort out\nthis perversity, but I decided not to try to recreate the Bash interpreter to\nbeautify a script. This means there will be some border cases this Python\nprogram won't be able to process. But in tests with large Linux system\nBash scripts, its error-free score was ~99%.\n\n## Installation\n\n### Using pip\n\n```shell\npip install beautysh\n```\n\n### Using Nix (recommended for development)\n\n```shell\nnix run github:lovesegfault/beautysh -- --help\n```\n\nOr add to your `flake.nix`:\n\n```nix\n{\n inputs.beautysh.url = \"github:lovesegfault/beautysh\";\n # ...\n}\n```\n\n### From source with uv\n\n```shell\ngit clone https://github.com/lovesegfault/beautysh\ncd beautysh\nuv sync\n```\n\n## Usage\n\nYou can call Beautysh from the command line such as\n\n```shell\nbeautysh file1.sh file2.sh file3.sh\n```\n\nin which case it will beautify each one of the files.\n\n### Configuration\n\nBeautysh supports multiple configuration sources with the following priority (highest to lowest):\n\n1. **Command-line arguments** (highest priority)\n1. **pyproject.toml** - `[tool.beautysh]` section\n1. **EditorConfig** - `.editorconfig` files (lowest priority)\n\n#### pyproject.toml\n\n```toml\n[tool.beautysh]\nindent_size = 4\ntab = false\nbackup = false\ncheck = false\nforce_function_style = \"fnpar\" # Options: fnpar, fnonly, paronly\nvariable_style = \"braces\" # Options: braces\n```\n\n#### EditorConfig\n\nBeautysh respects [EditorConfig](https://editorconfig.org/) settings:\n\n```ini\n[*.sh]\nindent_style = space # or \"tab\"\nindent_size = 4\n```\n\nSupported EditorConfig properties:\n\n- `indent_style`: Maps to `--tab` flag (space/tab)\n- `indent_size`: Maps to `--indent-size` option\n\n### Command-Line Options\n\nAvailable flags are:\n\n```\n --indent-size INDENT_SIZE, -i INDENT_SIZE\n Sets the number of spaces to be used in indentation.\n --backup, -b Beautysh will create a backup file in the same path as\n the original.\n --check, -c Beautysh will just check the files without doing any\n in-place beautify.\n --tab, -t Sets indentation to tabs instead of spaces.\n --force-function-style FORCE_FUNCTION_STYLE, -s FORCE_FUNCTION_STYLE\n Force a specific Bash function formatting. See below\n for more info.\n --variable-style VARIABLE_STYLE\n Force a specific variable style. See below for options.\n --version, -v Prints the version and exits.\n --help, -h Print this help message.\n\nBash function styles that can be specified via --force-function-style are:\n fnpar: function keyword, open/closed parentheses, e.g. function foo()\n fnonly: function keyword, no open/closed parentheses, e.g. function foo\n paronly: no function keyword, open/closed parentheses, e.g. foo()\n\nVariable styles that can be specified via --variable-style are:\n braces: transform $VAR to ${VAR} for consistency (e.g., $HOME becomes ${HOME})\n Note: Special variables ($?, $1, etc.) and parameter expansions\n (${VAR:-default}) are left unchanged.\n```\n\nYou can also call beautysh as a module:\n\n```python3\nfrom beautysh import Beautify\n\nsource = \"my_string\"\n\nresult, error = Beautify().beautify_string(source)\n```\n\nAs written, beautysh can beautify large numbers of Bash scripts when called\nfrom a variety of means,including a Bash script:\n\n```shell\n#!/bin/sh\n\nfor path in `find /path -name '*.sh'`\ndo\n beautysh $path\ndone\n```\n\nAs well as the more obvious example:\n\n```shell\n$ beautysh *.sh\n```\n\n> **CAUTION**: Because Beautysh overwrites all the files submitted to it, this\n> could have disastrous consequences if the files include some of the\n> increasingly common Bash scripts that have appended binary content (a regime\n> where Beautysh has undefined behaviour ). So please \u2014 back up your files,\n> and don't treat Beautysh as a harmless utility. Even if that is true\n> most of the time.\n\nBeautysh handles Bash here-docs with care(and there are probably some\nborder cases it doesn't handle). The basic idea is that the originator knew what\nformat he wanted in the here-doc, and a beautifier shouldn't try to outguess\nhim. So Beautysh does all it can to pass along the here-doc content\nunchanged:\n\n```shell\nif true\nthen\n\n echo \"Before here-doc\"\n\n # Insert 2 lines in file, then save.\n #--------Begin here document-----------#\nvi $TARGETFILE <<x23LimitStringx23\ni\nThis is line 1 of the example file.\nThis is line 2 of the example file.\n^[\nZZ\nx23LimitStringx23\n #----------End here document-----------#\n\n echo \"After here-doc\"\n\nfi\n```\n\nSpecial comments `@formatter:off` and `@formatter:on` are available to disable formatting around a block of statements.\n\n```shell\n# @formatter:off\ncommand \\\n --option1 \\\n --option2 \\\n --option3 \\\n# @formatter:on\n\n```\n\nThis takes inspiration from the Eclipse feature.\n\n## Development\n\n### Using Nix (recommended)\n\nThe easiest way to start developing is with Nix:\n\n```shell\n# Enter development shell with all dependencies\nnix develop\n\n# Run tests\npytest tests/\n\n# Run type checker\nmypy .\n\n# Run linter\nflake8 .\n\n# Format code\nnix fmt\n\n# Run all pre-commit checks\npre-commit run --all-files\n```\n\nThe development shell provides:\n\n- Python 3.12 with all dependencies\n- Editable install (changes to code are immediately reflected)\n- All development tools (pytest, mypy, flake8, black, isort)\n- Pre-commit hooks automatically installed\n\n### Using uv\n\n```shell\n# Install dependencies\nuv sync\n\n# Activate virtual environment and run tests\nuv run pytest tests/\n```\n\n## Contributing\n\nContributions are welcome and appreciated, however test cases must be added to\nprevent regression. Adding a test case is easy, and involves the following:\n\n1. Create a file `tests/fixtures/my_test_name_raw.sh` containing the unformatted version\n of your test case.\n1. Create a file `tests/fixtures/my_test_name_formatted.sh` containing the formatted version\n of your test case.\n1. Register your test case in `tests/test_integration.py`. It should look\n something like this:\n\n```python\ndef test_my_test_name(fixture_dir):\n assert_formatting(fixture_dir, \"my_test_name\")\n```\n\nBefore submitting a PR, please ensure:\n\n- All tests pass: `pytest tests/`\n- Code is formatted: `nix fmt` (or `black . && isort .`)\n- Type checking passes: `mypy .`\n- Linting passes: `flake8 .`\n\nOr simply run all checks at once:\n\n```shell\npre-commit run --all-files\n```\n\n______________________________________________________________________\n\nOriginally written by [Paul Lutus](http://arachnoid.com/python/beautify_bash_program.html)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Bash beautifier for the masses.",
"version": "6.3.1",
"project_urls": {
"Homepage": "https://github.com/lovesegfault/beautysh",
"Repository": "https://github.com/lovesegfault/beautysh"
},
"split_keywords": [
"auto",
" bash",
" beautifier",
" beautify",
" script",
" shell"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "edb87cfbb4ed8e01e40ccfc7f908cfd1d99742c519fec67be2ad67b4c52ad243",
"md5": "d1142b13cf2b98cac69db68d42bb4c35",
"sha256": "5f389fbd16c7bb330181acc0a0515ed4e2ae852569855e26aa3a332e59a5df88"
},
"downloads": -1,
"filename": "beautysh-6.3.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "d1142b13cf2b98cac69db68d42bb4c35",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 12084,
"upload_time": "2025-10-30T07:28:20",
"upload_time_iso_8601": "2025-10-30T07:28:20.378810Z",
"url": "https://files.pythonhosted.org/packages/ed/b8/7cfbb4ed8e01e40ccfc7f908cfd1d99742c519fec67be2ad67b4c52ad243/beautysh-6.3.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "345a4acfcfecca6fe9e1dc25f71f39fea011672569040e5f816e36f55f48e588",
"md5": "56e128988e30a7bff39f6fa95d9bb628",
"sha256": "58eb9d67627c09d0565e0e77892c5ff7cb7ba086cdac7e960452757daff14410"
},
"downloads": -1,
"filename": "beautysh-6.3.1.tar.gz",
"has_sig": false,
"md5_digest": "56e128988e30a7bff39f6fa95d9bb628",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 54260,
"upload_time": "2025-10-30T07:28:21",
"upload_time_iso_8601": "2025-10-30T07:28:21.822980Z",
"url": "https://files.pythonhosted.org/packages/34/5a/4acfcfecca6fe9e1dc25f71f39fea011672569040e5f816e36f55f48e588/beautysh-6.3.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-30 07:28:21",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "lovesegfault",
"github_project": "beautysh",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "beautysh"
}