Skip to main content

PyPI version Supported Python versions Downloads License pre-commit.ci status

shfmt-py

pip install shfmt-py puts shfmt, the shell script formatter, on the PATH of your Python environment and gives you a ready-made pre-commit hook.

This is packaging only — no patches, no Python API, nothing to import, no python -m shfmt. On common platforms the wheel bundles upstream's binary; elsewhere the build downloads and checksum-verifies it, or — where no build is pinned for your platform — copies an existing shfmt from your PATH unverified ("How the binary gets installed" below has the details). For what shfmt does and which flags it takes, run shfmt --help or read the upstream docs.

Modeled after shellcheck-py, adapted for shfmt.

Install

pip install shfmt-py

Or as a standalone tool, isolated from any project environment:

uv tool install shfmt-py
# or
pipx install shfmt-py

Requires Python 3.9 or newer; the binary itself has no Python runtime dependency. CI covers CPython 3.9, 3.13 and 3.14 on Linux, macOS (arm64 and x86_64) and Windows.

The distribution installs exactly one executable — shfmt, or shfmt.exe on Windows — into the environment's scripts directory, so it is on PATH whenever that environment is active. pip uninstall shfmt-py removes it again.

Pre-commit hook

Add to .pre-commit-config.yaml:

- repo: https://github.com/MaxWinterstein/shfmt-py
  rev: v4.1.0
  hooks:
    - id: shfmt

rev is the shfmt-py release tag, not the shfmt version. pre-commit autoupdate moves it to the newest tag; pre-commit run --all-files formats the whole repository.

The hook runs on every file identify tags as shell, excluding csh and tcsh. It defaults to args: [-w], which rewrites files in place.

Overriding args

pre-commit replaces the default args; it does not extend them. Drop -w by accident and shfmt prints the formatted script to stdout, changes nothing and exits 0 — the hook goes green while your files stay unformatted. So re-add it:

- repo: https://github.com/MaxWinterstein/shfmt-py
  rev: v4.1.0
  hooks:
    - id: shfmt
      args: [-w, -i, "2", -ci, -bn] # -w must be re-added

Those flags — two-space indent, indented case arms, binary operators allowed to start a line — are the set the upstream manual describes as closely resembling Google's shell style. Quote the 2: pre-commit expects every argument to be a string, and YAML would otherwise make it an integer.

For a check-only run that reports instead of rewriting, swap the args above for:

    - id: shfmt
      args: [-d] # print a diff and fail; no -w on purpose

EditorConfig

shfmt reads formatting options from .editorconfig. Two of its behaviors matter for hook users:

  • Any parser or printer flag turns EditorConfig off entirely-i, -ci, -s, -ln and friends disable every EditorConfig formatting option, not just the one you overrode. -w, -d and -l are generic flags and leave it alone, so the default args: [-w] keeps .editorconfig in charge. Configure your style in one place, not both.
  • ignore = true is skipped for explicitly named files, and pre-commit always names files explicitly. Use args: [-w, --apply-ignore] to honor it, or pre-commit's own exclude:.

Hook installs need network access

pre-commit installs language: python hooks by running pip install . inside its own clone, so it never uses the published wheels. Installing the hook therefore downloads the binary from the mvdan/sh GitHub release the first time a given rev is used, and caches the result afterwards. Restricted runners need github.com and its release-asset host (*.githubusercontent.com) reachable — a PyPI mirror is not enough.

Command line

shfmt --version    # the upstream version this release bundles
shfmt -w script.sh # format in place
shfmt -d .         # print a diff, exit 1 if anything differs
shfmt -l .         # list files that differ, exit 1 if any do

shfmt --help prints the full flag list. Dialects, EditorConfig keys and the default style are upstream's documentation, not this project's: see mvdan/sh and its man page source (or run man shfmt).

How the binary gets installed

Three paths, in this order:

  1. A matching wheel. Published for Linux x86_64 and aarch64 (manylinux2014), macOS arm64 and x86_64, and Windows amd64. The binary is already inside the wheel, so nothing is fetched at install time and a PyPI mirror is enough.
  2. From source, platform in the download table. The other platforms this package pins a download for — 32-bit Windows, Linux armv7 (hosts whose uname -m reports armv7l), Cygwin, and musl-based distros such as Alpine, which use the ordinary static Linux binaries — plus any install that bypasses wheels (pip install --no-binary :all: shfmt-py, or pre-commit). The build downloads the official release asset and verifies it against a sha256 pinned in setup.py. A mismatch aborts the install, and if GitHub is unreachable the install fails rather than silently using something else.
  3. From source, platform not in the download table. FreeBSD, illumos, 32-bit x86 Linux, older 32-bit ARM and other architectures with no pinned download here: the build copies whatever shfmt it finds on your PATH (on Windows it must be a real .exe). That copy is neither checksummed nor version-checked, so it may differ from the version this release pins — shfmt --version is worth a look. With no shfmt on PATH, the install fails with an error telling you to install one, instead of installing something broken.

For air-gapped environments, build one wheel per target platform on a machine that can reach GitHub (python -m build --wheel; on Linux set _PYTHON_HOST_PLATFORM=manylinux2014_x86_64, as the release workflow does, or the wheel comes out tagged linux_x86_64) and serve them from your internal index. That covers pip / uv / pipx installs only: pre-commit builds the hook from source and still reaches for GitHub, so an air-gapped runner additionally needs a mirror of the mvdan/sh release asset or a pre-populated ~/.cache/pre-commit.

Versioning

Which shfmt do you have? Ask the binary — shfmt --version is always right. Before installing, read SHFMT_VERSION in setup.py at that release's tag — https://github.com/MaxWinterstein/shfmt-py/blob/vX.Y.Z/setup.py.

shfmt-py is independently versioned; the PyPI version does not mirror the bundled shfmt version.

  • Major — breaking change to shfmt-py itself (e.g. dropping a Python version, renaming the pre-commit hook id).
  • Minor — new upstream shfmt release bundled.
  • Patch — wrapper-only fix (hash regeneration, CI changes affecting users, etc.).

Releases 3.x.y.z and earlier used a 4-segment scheme aligned with upstream shfmt3.13.0.3 bundled shfmt 3.13.0. From v4.0.0 onwards shfmt-py follows standard semver.

FAQ

The hook passes but nothing gets formatted.

You set args: without -w. pre-commit replaces the default args: [-w] instead of extending it, so re-add -w to your list.

shfmt: command not found after pip install.

The executable lands in the target environment's scripts directory. Activate that virtualenv, or use uv tool install shfmt-py / pipx install shfmt-py to get it on your user PATH. If your OS package manager also ships shfmt, PATH order decides which one runs — shfmt --version tells you which one you got.

It won't get updated via e.g. Renovate Bot.

Releases v4.0.0 and onwards use standard semver — no special Renovate config needed. For older 3.x.y.z releases you'll need "versioning": "pep440" (or see shfmt-py/update-via-renovate). For the pre-commit hook, pre-commit autoupdate works either way.

I get something like SSL: CERTIFICATE_VERIFY_FAILED on macOS.

This only happens on the from-source paths that download from GitHub at build time — which include every pre-commit hook install — never when a wheel is used. Install certificates with e.g. "/Applications/Python 3.x/Install Certificates.command" for the Python you are installing with. See this MerossIot comment or this Stack Overflow answer for a solution.

Issues

Formatting behavior, flags and feature requests belong upstream, at mvdan/sh issues. Packaging, wheels, platform coverage and the hook definition belong in this project's issue tracker.

License

shfmt-py is MIT licensed; see LICENSE. The shfmt binary it ships or downloads is the work of the mvdan/sh project and is redistributed unmodified under its own BSD-3-Clause license.

Download files

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

Source Distribution

shfmt_py-4.1.0.tar.gz (29.9 kB view details)

Uploaded Source

Built Distributions

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

shfmt_py-4.1.0-py2.py3-none-win_amd64.whl (1.7 MB view details)

Uploaded Python 2Python 3Windows x86-64

shfmt_py-4.1.0-py2.py3-none-manylinux2014_x86_64.whl (1.6 MB view details)

Uploaded Python 2Python 3

shfmt_py-4.1.0-py2.py3-none-manylinux2014_aarch64.whl (1.4 MB view details)

Uploaded Python 2Python 3

shfmt_py-4.1.0-py2.py3-none-macosx_11_0_arm64.whl (1.4 MB view details)

Uploaded Python 2Python 3macOS 11.0+ ARM64

shfmt_py-4.1.0-py2.py3-none-macosx_10_9_x86_64.whl (1.6 MB view details)

Uploaded Python 2Python 3macOS 10.9+ x86-64

File details

Details for the file shfmt_py-4.1.0.tar.gz.

File metadata

  • Download URL: shfmt_py-4.1.0.tar.gz
  • Upload date:
  • Size: 29.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for shfmt_py-4.1.0.tar.gz
Algorithm Hash digest
SHA256 e863c07885e2e976b1441e38f660b3262a87a52bf84124415b866e48eed7c00b
MD5 c441e7ed60ffc19665d3ef028af01fc0
BLAKE2b-256 9e2fc9616d3716a8e75a4c36427bede49d61b68c2d1595a76b3de2347e040fa7

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0.tar.gz:

Publisher: python-publish.yml on MaxWinterstein/shfmt-py

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

File details

Details for the file shfmt_py-4.1.0-py2.py3-none-win_amd64.whl.

File metadata

  • Download URL: shfmt_py-4.1.0-py2.py3-none-win_amd64.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: Python 2, Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for shfmt_py-4.1.0-py2.py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 7e5c55ac4b619fc4d215259459ac80e8513735245ec621ca290574fa7ab2811d
MD5 7af1f7826ae39a32bfb795711056be5f
BLAKE2b-256 e0e8be46d2141ec18d30c713258c97c4a8d85f5e39cedf8c9b2ba642c28a2199

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0-py2.py3-none-win_amd64.whl:

Publisher: python-publish.yml on MaxWinterstein/shfmt-py

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

File details

Details for the file shfmt_py-4.1.0-py2.py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for shfmt_py-4.1.0-py2.py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 4f1573856d84725f148175ef56532365dc4a7e5332e27c31b6c82be544ad2bda
MD5 cd52f8c54f4d43a433c6366acd47351c
BLAKE2b-256 0fb2101c166f6f1895a7e4b2062d350bf156097087ffc95f772877e51c04358d

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0-py2.py3-none-manylinux2014_x86_64.whl:

Publisher: python-publish.yml on MaxWinterstein/shfmt-py

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

File details

Details for the file shfmt_py-4.1.0-py2.py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for shfmt_py-4.1.0-py2.py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 887a7a04a297302f3472edaeabb5f33f146d9a681234743acc5932a2a39bdb22
MD5 e197538064d60111253f293e9ecc5a69
BLAKE2b-256 ff910bc95e1f3ba2b591986701d00c8ee80eef228fb08c338a873d0df3444092

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0-py2.py3-none-manylinux2014_aarch64.whl:

Publisher: python-publish.yml on MaxWinterstein/shfmt-py

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

File details

Details for the file shfmt_py-4.1.0-py2.py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for shfmt_py-4.1.0-py2.py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8e0201449fc35f3cf16abed3189f1de0c5c30d5c4c989b8d367fd5cd1d35e779
MD5 20d4751696b9b05af3f56b5beeefbe4f
BLAKE2b-256 3603e8f10813c498247f6ddff63c82e4bb351fda188ec73e3821433f913cb651

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0-py2.py3-none-macosx_11_0_arm64.whl:

Publisher: python-publish.yml on MaxWinterstein/shfmt-py

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

File details

Details for the file shfmt_py-4.1.0-py2.py3-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for shfmt_py-4.1.0-py2.py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 7186d0cdb21d8a5e6cb647c1b0ae7ee264751be3cac755277d060df90b1cfa8f
MD5 7e1736a62b7fbcb79b9071e6bb79d320
BLAKE2b-256 88e11d0129c8baaf943921a8b1402fc9bb2a1fc9753a35ddc068a639828e91d8

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0-py2.py3-none-macosx_10_9_x86_64.whl:

Publisher: python-publish.yml on MaxWinterstein/shfmt-py

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

Release history Release notifications | RSS feed

This release

4.1.0 This release

6 files

4.0.0

6 files

3.13.0.3

6 files

3.13.0.2

6 files

3.13.0.1

1 file

3.12.0.2

1 file

3.11.0.2

1 file

3.7.0.1

1 file

3.4.3.1

1 file

3.3.1.8

1 file

3.3.1.7

1 file

3.3.1.6

1 file

3.3.1.5

1 file

3.3.1.4

1 file

3.3.1.3

1 file

3.3.1.2

1 file

3.3.1.1

1 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