mrkeyoor.com_
Tue 22 Sept 22:34 UTC
PyPIUtilsupdated 22 Sept 2026

tree-sitter-bash review

tree-sitter-bash 0.25.1 packages the Bash grammar and compiled Python language binding used by tree-sitter. With a compatible runtime, it produces concrete syntax trees for complete or broken shell source, with byte ranges, redirects, expansions, pipelines, functions, missing nodes, and errors. Version 0.25.1 fixes false arithmetic-expansion parsing around doubled parentheses and moves valid arithmetic compound commands into the correct grammar branch. The package exports a language capsule and highlight query; it does not execute shell, lint code, or resolve variable meaning.

Verdict

tree-sitter-bash 0.25.1 installed in 0.2 seconds and used 2 MB in our sandbox, but direct `import _binding` failed with `ModuleNotFoundError`; the supported import is `tree_sitter_bash`. Install it for typed, incremental Bash syntax trees after pin-testing the separate runtime, not for shell execution, lint findings, or semantic security claims.

We installed it

Lab card: what happened when we installed tree-sitter-bashScreenshot of tree-sitter-bash documentation
Install✓ · 0.2s1 package on disk · 2 MB
Importimport _binding · compiled extensions · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does tree-sitter-bash install cleanly?

Yes. In a fresh container with an empty cache, pip install tree-sitter-bash finished in 0.2s, leaving 1 package and 2 MB on disk. pip-audit reported no known vulnerabilities.

What does tree-sitter-bash need to run?

Python >=3.10, and a platform wheel with compiled extensions. In our run import _binding failed, so it needs extra system packages, and the package ships py.typed for type checkers.

tree-sitter-bash or tree-sitter: which should you use?

tree-sitter: Use the core runtime when you already have or build a Bash language capsule another way. tree-sitter-bash 0.25.1 installed in 0.2 seconds and used 2 MB in our sandbox, but direct import _binding failed with ModuleNotFoundError; the supported import is tree_sitter_bash.

When should you not use tree-sitter-bash?

You need bug or security findings. ShellCheck understands many shell mistakes, while this grammar only describes syntax.

API stability3/5The Python package has a narrow typed export surface: `language()` plus the lazy `HIGHLIGHTS_QUERY` string. Downstream queries, however, bind directly to grammar node types and field names such as `command`, `function_definition`, and `variable_assignment`. Those names can move with grammar releases. The 0.25.1 `[core]` dependency still points at tree-sitter 0.24 despite the grammar's ABI 15, so version compatibility is not self-enforcing.
Docs2/5The repository README accurately identifies a Bash grammar, links Bash and POSIX references, and documents contributor build and test commands. It provides no Python example for installation, language construction, parsing, queries, byte offsets, errors, or incremental edits. Users must combine py-tree-sitter docs with `__init__.py`, `node-types.json`, `grammar.js`, corpus fixtures, and `queries/highlights.scm` to build a correct integration.
Maintenance3/5Version 0.25.1 was released on December 2, 2025, and GitHub was pushed the same day. The unarchived official tree-sitter repository contains generated parsers, corpus tests, fuzzing, CI, and bindings for several languages. GitHub currently reports 76 open issues and pull requests. The broad release machinery is healthy evidence, but the stale Python core extra and roughly 9 months without a push lower confidence in packaging follow-through.
Ecosystem4/5PyPI reports 4,415,365 downloads in the recent week, while the same grammar is distributed for Node, Rust, Go, Swift, and C users. Tree-sitter queries, editor integrations, and multi-language code tools can reuse the same model. Python consumers still need a compatible core runtime and their own traversal or query layer, so the high distribution count should not be read as a ready-made Bash analysis product.

Use it if

  • An editor, indexer, code browser, or refactoring tool needs Bash nodes while a user is still typing invalid code.
  • Incremental reparsing and changed ranges matter after precisely tracked text edits.
  • The same query and traversal model should cover Bash plus other tree-sitter grammars.
  • Byte-accurate captures and the packaged highlight query are useful building blocks for syntax tooling.
Skip it if

Setup reality

We installed tree-sitter-bash 0.25.1 in 0.2 seconds in a fresh Python 3.12 Bookworm sandbox. It left 1 package and 2 MB on disk. The package declares 1 dependency entry, requires Python 3.10 or newer, includes compiled .so extensions, ships py.typed, and uses the MIT license. pip-audit found 0 known vulnerabilities. Our direct import _binding probe failed with ModuleNotFoundError: No module named '_binding'.

That failure names an internal extension incorrectly, not the documented public module. The package's tree_sitter_bash/__init__.py imports ._binding relatively and exports language; user code should import tree_sitter_bash. Parsing also requires the separate tree-sitter package. The [core] extra in 0.25.1 specifies the 0.24 line, while the published grammar reports ABI 15. Pin a runtime proven to accept that ABI, such as the currently available tree-sitter 0.26.0, and run a construction test in CI.

Wheels cover common CPython 3.10+ macOS, Windows, glibc Linux, and musl Linux targets, including several Arm builds. A platform without one must compile C sources and needs a working compiler plus Python build headers. Parser input should remain bytes. start_byte and end_byte are byte offsets, and point columns also count bytes; slicing a decoded Python string breaks after non-ASCII text. Store the original byte buffer beside the tree.

A returned tree can contain ERROR and missing nodes, so check root_node.has_error before trusting an extraction. Incremental reuse only works when Tree.edit() receives exact old and new byte and point coordinates, then the edited old tree is supplied to parse. Query node and field names are grammar API and may change between releases. Compile every query against the pinned grammar in tests, and use ShellCheck or execution-aware tooling for conclusions about behavior or safety.

Patterns

Load the grammar into py-tree-sitter create-bash-parser

import tree_sitter_bash
from tree_sitter import Language, Parser

BASH = Language(tree_sitter_bash.language())
parser = Parser(BASH)

Version 0.25.1 exposes `tree_sitter_bash.language()`. Pin a tree-sitter runtime that accepts its ABI 15 capsule and test construction in CI.

Parse a Bash byte buffer parse-bash-bytes

source = b'#!/usr/bin/env bash\necho "$HOME"\n'
tree = parser.parse(source)
root = tree.root_node
print(root.type, root.start_point, root.end_point)

Keep `source` as bytes. Tree-sitter byte offsets and point columns do not index a decoded Unicode string correctly after non-ASCII characters.

Check for syntax recovery nodes reject-error-tree

tree = parser.parse(source)
if tree.root_node.has_error:
    raise ValueError('Bash tree contains an ERROR or missing node')

Parsing malformed text still returns a Tree. `has_error` is the first check before treating captures as authoritative.

Traverse structural nodes recursively walk-named-children

def walk(node):
    yield node
    for child in node.named_children:
        yield from walk(child)

for node in walk(tree.root_node):
    print(node.type, node.start_byte, node.end_byte)

`named_children` leaves out punctuation and operators. Use `children` when delimiters such as pipes or semicolons are part of the analysis.

Decode one node by byte range slice-node-source

def text_of(node, source_bytes):
    return source_bytes[node.start_byte:node.end_byte].decode('utf-8')

print(text_of(tree.root_node, source))

Slice the original bytes first and decode the slice. Applying byte offsets to Python `str` can return the wrong text.

Capture shell function names query-functions

from tree_sitter import Query, QueryCursor

query = Query(BASH, '(function_definition name: (word) @function.name)')
captures = QueryCursor(query).captures(tree.root_node)
names = [text_of(node, source) for node in captures.get('function.name', [])]

Query construction fails if a node or field name does not exist. Compile queries during startup tests against the pinned 0.25.1 grammar.

Capture syntactic command names query-command-names

query = Query(BASH, '(command name: (command_name) @command.name)')
captures = QueryCursor(query).captures(tree.root_node)
for node in captures.get('command.name', []):
    print(text_of(node, source))

A capture is source syntax, not the executable ultimately run after functions, aliases, expansions, `eval`, or PATH resolution.

Read assignment names and source values query-variable-assignments

query = Query(BASH, '''
(variable_assignment
  name: (variable_name) @name
  value: (_) @value)
''')
for _, match in QueryCursor(query).matches(tree.root_node):
    print(text_of(match['name'][0], source), text_of(match['value'][0], source))

Captured values may contain variables and command substitutions. The text is not an evaluated shell value.

Describe an edit before reparsing apply-incremental-edit

old_source = b'echo hi\n'
old_tree = parser.parse(old_source)
old_tree.edit(
    start_byte=0, old_end_byte=4, new_end_byte=6,
    start_point=(0, 0), old_end_point=(0, 4), new_end_point=(0, 6),
)
new_tree = parser.parse(b'printf hi\n', old_tree)

All 6 coordinates must describe the same replacement exactly. Incorrect byte or point values can invalidate incremental reuse.

Inspect syntax affected by an edit list-changed-ranges

for changed in old_tree.changed_ranges(new_tree):
    print(
        changed.start_byte, changed.end_byte,
        changed.start_point, changed.end_point,
    )

Changed syntax ranges may extend beyond the literal replacement because an edit can alter the surrounding parse structure.

Run the packaged highlight captures load-highlight-query

query = Query(BASH, tree_sitter_bash.HIGHLIGHTS_QUERY)
captures = QueryCursor(query).captures(tree.root_node)
for capture_name, nodes in captures.items():
    for node in nodes:
        print(capture_name, node.start_point, node.end_point)

Highlight captures are semantic labels rather than colors. A renderer must map them to a theme and decide how overlapping captures win.

Locate the smallest named node at a byte range find-node-at-byte

start = source.index(b'echo')
node = tree.root_node.named_descendant_for_byte_range(start, start + 4)
print(node.type, text_of(node, source))

Editor protocols often use UTF-16 positions. Convert them to offsets in the same UTF-8 byte buffer before querying the tree.

Alternatives

PackageRegistryPick it when
tree-sitterPyPIUse the core runtime when you already have or build a Bash language capsule another way.
tree-sitter-language-packPyPIUse it when one Python dependency should provide many prebuilt grammars, including Bash.
bashlexPyPIUse it for a Python-focused Bash parser when incremental editor parsing is unnecessary.
shfmt-pyPyPIUse it when formatting and a distributable shell syntax checker are the actual deliverables.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.