QueryList Filtering¶
libtmux uses QueryList to enable Django-style filtering on tmux objects.
Every collection (server.sessions, session.windows, window.panes) returns
a QueryList, letting you filter sessions, windows, and panes with a fluent,
chainable API.
Basic Filtering¶
The filter() method accepts keyword arguments with optional lookup suffixes:
>>> server.sessions
[Session($... ...)]
Exact Match¶
The default lookup is exact:
>>> # These are equivalent
>>> server.sessions.filter(session_name=session.session_name)
[Session($... ...)]
>>> server.sessions.filter(session_name__exact=session.session_name)
[Session($... ...)]
Contains and Startswith¶
Use suffixes for partial matching:
>>> # Create windows for this example
>>> w1 = session.new_window(window_name="api-server")
>>> w2 = session.new_window(window_name="api-worker")
>>> w3 = session.new_window(window_name="web-frontend")
>>> # Windows containing 'api'
>>> api_windows = session.windows.filter(window_name__contains='api')
>>> len(api_windows) >= 2
True
>>> # Windows starting with 'web'
>>> web_windows = session.windows.filter(window_name__startswith='web')
>>> len(web_windows) >= 1
True
>>> # Clean up
>>> w1.kill()
>>> w2.kill()
>>> w3.kill()
Available Lookups¶
Lookup |
Description |
|---|---|
|
Exact match (default) |
|
Case-insensitive exact match |
|
Substring match |
|
Case-insensitive substring |
|
Prefix match |
|
Case-insensitive prefix |
|
Suffix match |
|
Case-insensitive suffix |
|
Value in list |
|
Value not in list |
|
Regular expression match |
|
Case-insensitive regex |
Getting a Single Item¶
Use get() to retrieve exactly one matching item:
>>> window = session.windows.get(window_id=session.active_window.window_id)
>>> window
Window(@... ..., Session($... ...))
If no match or multiple matches are found, get() raises an exception:
ObjectDoesNotExist- no matching object foundMultipleObjectsReturned- more than one object matches
You can provide a default value to avoid the exception:
>>> session.windows.get(window_name="nonexistent", default=None) is None
True
Chaining Filters¶
Filters can be chained for complex queries:
>>> # Create windows for this example
>>> w1 = session.new_window(window_name="feature-login")
>>> w2 = session.new_window(window_name="feature-signup")
>>> w3 = session.new_window(window_name="bugfix-typo")
>>> # Multiple conditions in one filter (AND)
>>> session.windows.filter(
... window_name__startswith='feature',
... window_name__endswith='signup'
... )
[Window(@... ...:feature-signup, Session($... ...))]
>>> # Chained filters (also AND)
>>> session.windows.filter(
... window_name__contains='feature'
... ).filter(
... window_name__contains='login'
... )
[Window(@... ...:feature-login, Session($... ...))]
>>> # Clean up
>>> w1.kill()
>>> w2.kill()
>>> w3.kill()
Case-Insensitive Filtering¶
Use i prefix variants for case-insensitive matching:
>>> # Create windows with mixed case
>>> w1 = session.new_window(window_name="MyApp-Server")
>>> w2 = session.new_window(window_name="myapp-worker")
>>> # Case-insensitive contains
>>> myapp_windows = session.windows.filter(window_name__icontains='MYAPP')
>>> len(myapp_windows) >= 2
True
>>> # Case-insensitive startswith
>>> session.windows.filter(window_name__istartswith='myapp')
[Window(@... ...:MyApp-Server, Session($... ...)), Window(@... ...:myapp-worker, Session($... ...))]
>>> # Clean up
>>> w1.kill()
>>> w2.kill()
Regex Filtering¶
For complex patterns, use regex lookups:
>>> # Create windows with version-like names
>>> w1 = session.new_window(window_name="app-v1.0")
>>> w2 = session.new_window(window_name="app-v2.0")
>>> w3 = session.new_window(window_name="app-beta")
>>> # Match version pattern
>>> versioned = session.windows.filter(window_name__regex=r'v\d+\.\d+$')
>>> len(versioned) >= 2
True
>>> # Case-insensitive regex
>>> session.windows.filter(window_name__iregex=r'BETA')
[Window(@... ...:app-beta, Session($... ...))]
>>> # Clean up
>>> w1.kill()
>>> w2.kill()
>>> w3.kill()
Filtering by List Membership¶
Use in and nin (not in) for list-based filtering:
>>> # Create test windows
>>> w1 = session.new_window(window_name="dev")
>>> w2 = session.new_window(window_name="staging")
>>> w3 = session.new_window(window_name="prod")
>>> # Filter windows in a list of names
>>> target_envs = ["dev", "prod"]
>>> session.windows.filter(window_name__in=target_envs)
[Window(@... ...:dev, Session($... ...)), Window(@... ...:prod, Session($... ...))]
>>> # Filter windows NOT in a list
>>> non_prod = session.windows.filter(window_name__nin=["prod"])
>>> any(w.window_name == "prod" for w in non_prod)
False
>>> # Clean up
>>> w1.kill()
>>> w2.kill()
>>> w3.kill()
Filtering Across the Hierarchy¶
Filter at any level of the tmux hierarchy:
>>> # All panes across all windows in the server
>>> server.panes
[Pane(%... Window(@... ..., Session($... ...)))]
>>> # Filter panes by their window's name
>>> pane = session.active_pane
>>> pane
Pane(%... Window(@... ..., Session($... ...)))
Real-World Examples¶
Find all editor windows¶
>>> # Create sample windows
>>> w1 = session.new_window(window_name="vim-main")
>>> w2 = session.new_window(window_name="nvim-config")
>>> w3 = session.new_window(window_name="shell")
>>> # Find vim/nvim windows
>>> editors = session.windows.filter(window_name__iregex=r'n?vim')
>>> len(editors) >= 2
True
>>> # Clean up
>>> w1.kill()
>>> w2.kill()
>>> w3.kill()
Find windows by naming convention¶
>>> # Create windows following a naming convention
>>> w1 = session.new_window(window_name="project:frontend")
>>> w2 = session.new_window(window_name="project:backend")
>>> w3 = session.new_window(window_name="logs")
>>> # Find all project windows
>>> project_windows = session.windows.filter(window_name__startswith='project:')
>>> len(project_windows) >= 2
True
>>> # Get specific project window
>>> backend = session.windows.get(window_name='project:backend')
>>> backend.window_name
'project:backend'
>>> # Clean up
>>> w1.kill()
>>> w2.kill()
>>> w3.kill()
tmux-native Filtering with search_*()¶
QueryList.filter() runs in Python after tmux has returned every
row. For large servers, or when you only need a handful of matches,
ask tmux to apply the filter before libtmux builds objects. Every level
of the hierarchy ships a search_*() method that passes a format
expression to tmux:
Caller |
Method |
Underlying tmux |
|---|---|---|
|
||
|
||
|
||
|
||
|
||
|
The list_buffers() method also accepts a filter=
kwarg with the same semantics.
There is no search_clients() method; filter clients via the
clients accessor and Python-side
filter(). Filtering
clients in Python is usually enough because a server’s client
count is bounded by attached terminals, not by session/window/pane
fan-out.
Python-side vs. tmux-native¶
|
|
|
|---|---|---|
Where |
Python (after fetch) |
tmux server (before fetch) |
Filter language |
libtmux’s lookup operators ( |
tmux’s FORMATS grammar |
Round trips |
one (full list, then filter in memory) |
one (tmux returns only matches) |
Best for |
rich Python checks, set membership, post-fetch composition |
exact/glob matches over many rows |
Stability |
every libtmux version supports it |
requires tmux ≥ 3.2 (≥ 3.4 for |
Both are valid; pick based on data volume and the filter language you want.
Filter syntax¶
tmux’s filter language is the same one used in -F templates. Three
shapes cover most use cases:
>>> # Match by glob
>>> s_alpha = server.new_session(session_name='alpha-1')
>>> s_beta = server.new_session(session_name='beta-1')
>>> alphas = server.search_sessions(filter='#{m:alpha-*,#{session_name}}')
>>> [s.session_name for s in alphas]
['alpha-1']
>>> # Match by equality
>>> exact = server.search_sessions(
... filter='#{==:#{session_name},alpha-1}'
... )
>>> [s.session_name for s in exact]
['alpha-1']
>>> # Clean up
>>> s_alpha.kill()
>>> s_beta.kill()
#{e:...} evaluates an arithmetic expression; #{?cond,a,b} is the
conditional form. See man tmux for the full grammar.
The silent zero-match trap¶
A malformed filter expression is the single biggest footgun. tmux expands an
unclosed #{...} or an unknown format token to an empty string,
which the filter engine evaluates as “false” — every row is filtered
out and no stderr is emitted. A bad filter is indistinguishable
from a filter that genuinely matched nothing.
If search_*() returns empty unexpectedly:
Replace the filter with
#{m:*,#{session_name}}(or the equivalent for windows/panes). If that returns rows, the issue is filter syntax, not data.Expand the expression standalone via
display_message()to see what tmux actually produced:>>> result = server.display_message( ... '#{m:alpha-*,alpha-1}', get_text=True ... ) >>> result[0] '1'
A non-
1, non-empty result tells you the expression is parsing as text, not as a boolean.Cross-check the token name against the FORMATS section of
tmux(1)and against the version gate (see Format-Token Fields).
When to prefer which¶
Use search_*() when:
you have hundreds or thousands of windows/panes and only want a few,
your filter is a glob (
m:) or equality check (==:),you’re already in tmux-format thinking (writing
#{...}for a status-line template, for example).
Use .filter() when:
your filter needs Python types you can’t express in tmux format (set membership, complex regex, computed values from outside tmux),
you’re chaining multiple filters and prefer composing in Python,
you want predictable, version-independent semantics.
API Reference¶
See QueryList for the complete
QueryList API, and each search_*() method for the tmux-native filter
contract.