Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

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.0rc1
  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.0rc1
  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.0rc1.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.0rc1-py2.py3-none-win_amd64.whl (1.7 MB view details)

Uploaded Python 2Python 3Windows x86-64

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

Uploaded Python 2Python 3

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

Uploaded Python 2Python 3

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

Uploaded Python 2Python 3macOS 11.0+ ARM64

shfmt_py-4.1.0rc1-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.0rc1.tar.gz.

File metadata

  • Download URL: shfmt_py-4.1.0rc1.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.0rc1.tar.gz
Algorithm Hash digest
SHA256 726ae1e6a29e405d1a7cf00c86870bf6a01d2643d5978cab1cf0a1469cee2e1f
MD5 04cfb4c345d2a2bf9268e96bed674055
BLAKE2b-256 6327e814f21e891d8cd40d02e288d81ff72fb9bfdbfba97cb6bbbf0b1d4ab1b5

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0rc1.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.0rc1-py2.py3-none-win_amd64.whl.

File metadata

  • Download URL: shfmt_py-4.1.0rc1-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.0rc1-py2.py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 a8b0186e28241918869d9b604e5f02c9ca08070294baf2b153fb47da089b77d2
MD5 e085ede16ca39c6a71d91702e9cd54ef
BLAKE2b-256 3bef23deefe11b63fd58f9ce78c7584a11a910b02e1358b8795753e50440e4a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0rc1-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.0rc1-py2.py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for shfmt_py-4.1.0rc1-py2.py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 960becda7f61ac7c79bae607a0eefb9b475dc30c7baa0d71b020e6b634243f03
MD5 3ec617d8375fad5a8a116381b6ba3697
BLAKE2b-256 8c9c13fff08af19dcc05f0d502f071af13c56869477230e64c56b31f4708655a

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0rc1-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.0rc1-py2.py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for shfmt_py-4.1.0rc1-py2.py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 378f6f5c0542c27525c7b4bc6a3a4183b6f0e79bbc62042f9676b7b695af7905
MD5 eee16deef650385b43d193eb9468f1af
BLAKE2b-256 f7120fd99163b081d27479619e1e3f7668c03943e5d172195f1cecc047c37d65

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0rc1-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.0rc1-py2.py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for shfmt_py-4.1.0rc1-py2.py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 abf942d13cbd1d713a02263a2c61c3bde448671f90774e74d38b9076dc028ca1
MD5 f3be7f9fcfb156cbea1ba562deaa0377
BLAKE2b-256 1e5476735d67f197e30252b2bb25b08977e462f00c9bf371c6601a88a518f43e

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0rc1-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.0rc1-py2.py3-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for shfmt_py-4.1.0rc1-py2.py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 ddc5252244b04ead3b02d945487202c80f062db0722b2ecb89eed8227a2ee57b
MD5 248e2bea587fcf301b8fdaa68752ad35
BLAKE2b-256 e3f6f0a606d71aa04e4d80c30cd533dc79c967b9bad18952c4f46b8cb945befb

See more details on using hashes here.

Provenance

The following attestation bundles were made for shfmt_py-4.1.0rc1-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

4.1.0

6 files

This release

4.1.0rc1 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