Add jq and tmux expert skills (#414)
* Add jq and tmux expert skills for shell workflows Adds two practical, expert-level skills for bash/shell users: - jq: JSON querying and transformation with filtering, mapping, select, reduce, and shell pipeline integration - tmux: session/window/pane management, scripted layouts, automation patterns, and remote SSH workflows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(ci): Refresh PR checks --------- Co-authored-by: kostakost2 <kostakost2@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: sickn33 <sickn33@users.noreply.github.com>
This commit is contained in:
273
skills/jq/SKILL.md
Normal file
273
skills/jq/SKILL.md
Normal file
@@ -0,0 +1,273 @@
|
||||
---
|
||||
name: jq
|
||||
description: "Expert jq usage for JSON querying, filtering, transformation, and pipeline integration. Practical patterns for real shell workflows."
|
||||
category: development
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-03-28"
|
||||
author: kostakost2
|
||||
tags: [jq, json, shell, cli, data-transformation, bash]
|
||||
tools: [claude, cursor, gemini]
|
||||
---
|
||||
|
||||
# jq — JSON Querying and Transformation
|
||||
|
||||
## Overview
|
||||
|
||||
`jq` is the standard CLI tool for querying and reshaping JSON. This skill covers practical, expert-level usage: filtering deeply nested data, transforming structures, aggregating values, and composing `jq` into shell pipelines. Every example is copy-paste ready for real workflows.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Use when parsing JSON output from APIs, CLI tools (AWS, GitHub, kubectl, docker), or log files
|
||||
- Use when transforming JSON structure (rename keys, flatten arrays, group records)
|
||||
- Use when the user needs `jq` inside a bash script or one-liner
|
||||
- Use when explaining what a complex `jq` expression does
|
||||
|
||||
## How It Works
|
||||
|
||||
`jq` takes a filter expression and applies it to JSON input. Filters compose with pipes (`|`), and `jq` handles arrays, objects, strings, numbers, booleans, and `null` natively.
|
||||
|
||||
### Basic Selection
|
||||
|
||||
```bash
|
||||
# Extract a field
|
||||
echo '{"name":"alice","age":30}' | jq '.name'
|
||||
# "alice"
|
||||
|
||||
# Nested access
|
||||
echo '{"user":{"email":"a@b.com"}}' | jq '.user.email'
|
||||
|
||||
# Array index
|
||||
echo '[10, 20, 30]' | jq '.[1]'
|
||||
# 20
|
||||
|
||||
# Array slice
|
||||
echo '[1,2,3,4,5]' | jq '.[2:4]'
|
||||
# [3, 4]
|
||||
|
||||
# All array elements
|
||||
echo '[{"id":1},{"id":2}]' | jq '.[]'
|
||||
```
|
||||
|
||||
### Filtering with `select`
|
||||
|
||||
```bash
|
||||
# Keep only matching elements
|
||||
echo '[{"role":"admin"},{"role":"user"},{"role":"admin"}]' \
|
||||
| jq '[.[] | select(.role == "admin")]'
|
||||
|
||||
# Numeric comparison
|
||||
curl -s https://api.github.com/repos/owner/repo/issues \
|
||||
| jq '[.[] | select(.comments > 5)]'
|
||||
|
||||
# Test a field exists and is non-null
|
||||
jq '[.[] | select(.email != null)]'
|
||||
|
||||
# Combine conditions
|
||||
jq '[.[] | select(.active == true and .score >= 80)]'
|
||||
```
|
||||
|
||||
### Mapping and Transformation
|
||||
|
||||
```bash
|
||||
# Extract a field from every array element
|
||||
echo '[{"name":"alice","age":30},{"name":"bob","age":25}]' \
|
||||
| jq '[.[] | .name]'
|
||||
# ["alice", "bob"]
|
||||
|
||||
# Shorthand: map()
|
||||
jq 'map(.name)'
|
||||
|
||||
# Build a new object per element
|
||||
jq '[.[] | {user: .name, years: .age}]'
|
||||
|
||||
# Add a computed field
|
||||
jq '[.[] | . + {senior: (.age > 28)}]'
|
||||
|
||||
# Rename keys
|
||||
jq '[.[] | {username: .name, email_address: .email}]'
|
||||
```
|
||||
|
||||
### Aggregation and Reduce
|
||||
|
||||
```bash
|
||||
# Sum all values
|
||||
echo '[1, 2, 3, 4, 5]' | jq 'add'
|
||||
# 15
|
||||
|
||||
# Sum a field across objects
|
||||
jq '[.[].price] | add'
|
||||
|
||||
# Count elements
|
||||
jq 'length'
|
||||
|
||||
# Max / min
|
||||
jq 'max_by(.score)'
|
||||
jq 'min_by(.created_at)'
|
||||
|
||||
# reduce: custom accumulator
|
||||
echo '[1,2,3,4,5]' | jq 'reduce .[] as $x (0; . + $x)'
|
||||
# 15
|
||||
|
||||
# Group by field
|
||||
jq 'group_by(.department)'
|
||||
|
||||
# Count per group
|
||||
jq 'group_by(.status) | map({status: .[0].status, count: length})'
|
||||
```
|
||||
|
||||
### String Interpolation and Formatting
|
||||
|
||||
```bash
|
||||
# String interpolation
|
||||
jq -r '.[] | "\(.name) is \(.age) years old"'
|
||||
|
||||
# Format as CSV (no header)
|
||||
jq -r '.[] | [.name, .age, .email] | @csv'
|
||||
|
||||
# Format as TSV
|
||||
jq -r '.[] | [.name, .score] | @tsv'
|
||||
|
||||
# URL-encode a value
|
||||
jq -r '.query | @uri'
|
||||
|
||||
# Base64 encode
|
||||
jq -r '.data | @base64'
|
||||
```
|
||||
|
||||
### Working with Keys and Paths
|
||||
|
||||
```bash
|
||||
# List all top-level keys
|
||||
jq 'keys'
|
||||
|
||||
# Check if key exists
|
||||
jq 'has("email")'
|
||||
|
||||
# Delete a key
|
||||
jq 'del(.password)'
|
||||
|
||||
# Delete nested keys from every element
|
||||
jq '[.[] | del(.internal_id, .raw_payload)]'
|
||||
|
||||
# Recursive descent: find all values for a key anywhere in tree
|
||||
jq '.. | .id? // empty'
|
||||
|
||||
# Get all leaf paths
|
||||
jq '[paths(scalars)]'
|
||||
```
|
||||
|
||||
### Conditionals and Error Handling
|
||||
|
||||
```bash
|
||||
# if-then-else
|
||||
jq 'if .score >= 90 then "A" elif .score >= 80 then "B" else "C" end'
|
||||
|
||||
# Alternative operator: use fallback if null or false
|
||||
jq '.nickname // .name'
|
||||
|
||||
# try-catch: skip errors instead of halting
|
||||
jq '[.[] | try .nested.value catch null]'
|
||||
|
||||
# Suppress null output with // empty
|
||||
jq '.[] | .optional_field // empty'
|
||||
```
|
||||
|
||||
### Practical Shell Integration
|
||||
|
||||
```bash
|
||||
# Read from file
|
||||
jq '.users' data.json
|
||||
|
||||
# Compact output (no whitespace) for further piping
|
||||
jq -c '.[]' records.json | while IFS= read -r record; do
|
||||
echo "Processing: $record"
|
||||
done
|
||||
|
||||
# Pass a shell variable into jq
|
||||
STATUS="active"
|
||||
jq --arg s "$STATUS" '[.[] | select(.status == $s)]'
|
||||
|
||||
# Pass a number
|
||||
jq --argjson threshold 42 '[.[] | select(.value > $threshold)]'
|
||||
|
||||
# Slurp multiple JSON lines into an array
|
||||
jq -s '.' records.ndjson
|
||||
|
||||
# Multiple files: slurp all into one array
|
||||
jq -s 'add' file1.json file2.json
|
||||
|
||||
# Null-safe pipeline from a command
|
||||
kubectl get pods -o json | jq '.items[] | {name: .metadata.name, status: .status.phase}'
|
||||
|
||||
# GitHub CLI: extract PR numbers
|
||||
gh pr list --json number,title | jq -r '.[] | "\(.number)\t\(.title)"'
|
||||
|
||||
# AWS CLI: list running instance IDs
|
||||
aws ec2 describe-instances \
|
||||
| jq -r '.Reservations[].Instances[] | select(.State.Name=="running") | .InstanceId'
|
||||
|
||||
# Docker: show container names and images
|
||||
docker inspect $(docker ps -q) | jq -r '.[] | "\(.Name)\t\(.Config.Image)"'
|
||||
```
|
||||
|
||||
### Advanced Patterns
|
||||
|
||||
```bash
|
||||
# Transpose an object of arrays to an array of objects
|
||||
# Input: {"names":["a","b"],"scores":[10,20]}
|
||||
jq '[.names, .scores] | transpose | map({name: .[0], score: .[1]})'
|
||||
|
||||
# Flatten one level
|
||||
jq 'flatten(1)'
|
||||
|
||||
# Unique by field
|
||||
jq 'unique_by(.email)'
|
||||
|
||||
# Sort, deduplicate and re-index
|
||||
jq '[.[] | .name] | unique | sort'
|
||||
|
||||
# Walk: apply transformation to every node recursively
|
||||
jq 'walk(if type == "string" then ascii_downcase else . end)'
|
||||
|
||||
# env: read environment variables inside jq
|
||||
export API_KEY=secret
|
||||
jq -n 'env.API_KEY'
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Always use `-r` (raw output) when passing `jq` results to shell variables or other commands to strip JSON string quotes
|
||||
- Use `--arg` / `--argjson` to inject shell variables safely — never interpolate shell variables directly into filter strings
|
||||
- Prefer `map(f)` over `[.[] | f]` for readability
|
||||
- Use `-c` (compact) for newline-delimited JSON pipelines; omit it for human-readable debugging
|
||||
- Test filters interactively with `jq -n` and literal input before embedding in scripts
|
||||
- Use `empty` to drop unwanted elements rather than filtering to `null`
|
||||
|
||||
## Security & Safety Notes
|
||||
|
||||
- `jq` is read-only by design — it cannot write files or execute commands
|
||||
- Avoid embedding untrusted JSON field values directly into shell commands; always quote or use `--arg`
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Problem:** `jq` outputs `null` instead of the expected value
|
||||
**Solution:** Check for typos in key names; use `keys` to inspect actual field names. Remember JSON is case-sensitive.
|
||||
|
||||
- **Problem:** Numbers are quoted as strings in the output
|
||||
**Solution:** Use `--argjson` instead of `--arg` when injecting numeric values.
|
||||
|
||||
- **Problem:** Filter works in the terminal but fails in a script
|
||||
**Solution:** Ensure the filter string uses single quotes in the shell to prevent variable expansion. Example: `jq '.field'` not `jq ".field"`.
|
||||
|
||||
- **Problem:** `add` returns `null` on an empty array
|
||||
**Solution:** Use `add // 0` or `add // ""` to provide a fallback default.
|
||||
|
||||
- **Problem:** Streaming large files is slow
|
||||
**Solution:** Use `jq --stream` or switch to `jstream`/`gron` for very large files.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `@bash-pro` — Wrapping jq calls in robust shell scripts
|
||||
- `@bash-linux` — General shell pipeline patterns
|
||||
- `@github-automation` — Using jq with GitHub CLI JSON output
|
||||
370
skills/tmux/SKILL.md
Normal file
370
skills/tmux/SKILL.md
Normal file
@@ -0,0 +1,370 @@
|
||||
---
|
||||
name: tmux
|
||||
description: "Expert tmux session, window, and pane management for terminal multiplexing, persistent remote workflows, and shell scripting automation."
|
||||
category: development
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-03-28"
|
||||
author: kostakost2
|
||||
tags: [tmux, terminal, multiplexer, sessions, shell, remote, automation]
|
||||
tools: [claude, cursor, gemini]
|
||||
---
|
||||
|
||||
# tmux — Terminal Multiplexer
|
||||
|
||||
## Overview
|
||||
|
||||
`tmux` keeps terminal sessions alive across SSH disconnects, splits work across multiple panes, and enables fully scriptable terminal automation. This skill covers session management, window/pane layout, keybinding patterns, and using `tmux` non-interactively from shell scripts — essential for remote servers, long-running jobs, and automated workflows.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Use when setting up or managing persistent terminal sessions on remote servers
|
||||
- Use when the user needs to run long-running processes that survive SSH disconnects
|
||||
- Use when scripting multi-pane terminal layouts (e.g., logs + shell + editor)
|
||||
- Use when automating `tmux` commands from bash scripts without user interaction
|
||||
|
||||
## How It Works
|
||||
|
||||
`tmux` has three hierarchy levels: **sessions** (top level, survives disconnects), **windows** (tabs within a session), and **panes** (splits within a window). Everything is controllable from outside via `tmux <command>` or from inside via the prefix key (`Ctrl-b` by default).
|
||||
|
||||
### Session Management
|
||||
|
||||
```bash
|
||||
# Create a new named session
|
||||
tmux new-session -s work
|
||||
|
||||
# Create detached (background) session
|
||||
tmux new-session -d -s work
|
||||
|
||||
# Create detached session and start a command
|
||||
tmux new-session -d -s build -x 220 -y 50 "make all"
|
||||
|
||||
# Attach to a session
|
||||
tmux attach -t work
|
||||
tmux attach # attaches to most recent session
|
||||
|
||||
# List all sessions
|
||||
tmux list-sessions
|
||||
tmux ls
|
||||
|
||||
# Detach from inside tmux
|
||||
# Prefix + d (Ctrl-b d)
|
||||
|
||||
# Kill a session
|
||||
tmux kill-session -t work
|
||||
|
||||
# Kill all sessions except the current one
|
||||
tmux kill-session -a
|
||||
|
||||
# Rename a session from outside
|
||||
tmux rename-session -t old-name new-name
|
||||
|
||||
# Switch to another session from outside
|
||||
tmux switch-client -t other-session
|
||||
|
||||
# Check if a session exists (useful in scripts)
|
||||
tmux has-session -t work 2>/dev/null && echo "exists"
|
||||
```
|
||||
|
||||
### Window Management
|
||||
|
||||
```bash
|
||||
# Create a new window in the current session
|
||||
tmux new-window -t work -n "logs"
|
||||
|
||||
# Create a window running a specific command
|
||||
tmux new-window -t work:3 -n "server" "python -m http.server 8080"
|
||||
|
||||
# List windows
|
||||
tmux list-windows -t work
|
||||
|
||||
# Select (switch to) a window
|
||||
tmux select-window -t work:logs
|
||||
tmux select-window -t work:2 # by index
|
||||
|
||||
# Rename a window
|
||||
tmux rename-window -t work:2 "editor"
|
||||
|
||||
# Kill a window
|
||||
tmux kill-window -t work:logs
|
||||
|
||||
# Move window to a new index
|
||||
tmux move-window -s work:3 -t work:1
|
||||
|
||||
# From inside tmux:
|
||||
# Prefix + c — new window
|
||||
# Prefix + , — rename window
|
||||
# Prefix + & — kill window
|
||||
# Prefix + n/p — next/previous window
|
||||
# Prefix + 0-9 — switch to window by number
|
||||
```
|
||||
|
||||
### Pane Management
|
||||
|
||||
```bash
|
||||
# Split pane vertically (left/right)
|
||||
tmux split-window -h -t work:1
|
||||
|
||||
# Split pane horizontally (top/bottom)
|
||||
tmux split-window -v -t work:1
|
||||
|
||||
# Split and run a command
|
||||
tmux split-window -h -t work:1 "tail -f /var/log/syslog"
|
||||
|
||||
# Select a pane by index
|
||||
tmux select-pane -t work:1.0
|
||||
|
||||
# Resize panes
|
||||
tmux resize-pane -t work:1.0 -R 20 # expand right by 20 cols
|
||||
tmux resize-pane -t work:1.0 -D 10 # shrink down by 10 rows
|
||||
tmux resize-pane -Z # toggle zoom (fullscreen)
|
||||
|
||||
# Swap panes
|
||||
tmux swap-pane -s work:1.0 -t work:1.1
|
||||
|
||||
# Kill a pane
|
||||
tmux kill-pane -t work:1.1
|
||||
|
||||
# From inside tmux:
|
||||
# Prefix + % — split vertical
|
||||
# Prefix + " — split horizontal
|
||||
# Prefix + arrow — navigate panes
|
||||
# Prefix + z — zoom/unzoom current pane
|
||||
# Prefix + x — kill pane
|
||||
# Prefix + {/} — swap pane with previous/next
|
||||
```
|
||||
|
||||
### Sending Commands to Panes Without Being Attached
|
||||
|
||||
```bash
|
||||
# Send a command to a specific pane and press Enter
|
||||
tmux send-keys -t work:1.0 "ls -la" Enter
|
||||
|
||||
# Run a command in a background pane without attaching
|
||||
tmux send-keys -t work:editor "vim src/main.py" Enter
|
||||
|
||||
# Send Ctrl+C to stop a running process
|
||||
tmux send-keys -t work:1.0 C-c
|
||||
|
||||
# Send text without pressing Enter (useful for pre-filling prompts)
|
||||
tmux send-keys -t work:1.0 "git commit -m '"
|
||||
|
||||
# Clear a pane
|
||||
tmux send-keys -t work:1.0 "clear" Enter
|
||||
|
||||
# Check what's in a pane (capture its output)
|
||||
tmux capture-pane -t work:1.0 -p
|
||||
tmux capture-pane -t work:1.0 -p | grep "ERROR"
|
||||
```
|
||||
|
||||
### Scripting a Full Workspace Layout
|
||||
|
||||
This is the most powerful pattern: create a fully configured multi-pane workspace from a single script.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SESSION="dev"
|
||||
|
||||
# Bail if session already exists
|
||||
tmux has-session -t "$SESSION" 2>/dev/null && {
|
||||
echo "Session $SESSION already exists. Attaching..."
|
||||
tmux attach -t "$SESSION"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Create session with first window
|
||||
tmux new-session -d -s "$SESSION" -n "editor" -x 220 -y 50
|
||||
|
||||
# Window 1: editor + test runner side by side
|
||||
tmux send-keys -t "$SESSION:editor" "vim ." Enter
|
||||
tmux split-window -h -t "$SESSION:editor"
|
||||
tmux send-keys -t "$SESSION:editor.1" "npm test -- --watch" Enter
|
||||
tmux select-pane -t "$SESSION:editor.0"
|
||||
|
||||
# Window 2: server logs
|
||||
tmux new-window -t "$SESSION" -n "server"
|
||||
tmux send-keys -t "$SESSION:server" "docker compose up" Enter
|
||||
tmux split-window -v -t "$SESSION:server"
|
||||
tmux send-keys -t "$SESSION:server.1" "tail -f logs/app.log" Enter
|
||||
|
||||
# Window 3: general shell
|
||||
tmux new-window -t "$SESSION" -n "shell"
|
||||
|
||||
# Focus first window
|
||||
tmux select-window -t "$SESSION:editor"
|
||||
|
||||
# Attach
|
||||
tmux attach -t "$SESSION"
|
||||
```
|
||||
|
||||
### Configuration (`~/.tmux.conf`)
|
||||
|
||||
```bash
|
||||
# Change prefix to Ctrl-a (screen-style)
|
||||
unbind C-b
|
||||
set -g prefix C-a
|
||||
bind C-a send-prefix
|
||||
|
||||
# Enable mouse support
|
||||
set -g mouse on
|
||||
|
||||
# Start window/pane numbering at 1
|
||||
set -g base-index 1
|
||||
setw -g pane-base-index 1
|
||||
|
||||
# Renumber windows when one is closed
|
||||
set -g renumber-windows on
|
||||
|
||||
# Increase scrollback buffer
|
||||
set -g history-limit 50000
|
||||
|
||||
# Use vi keys in copy mode
|
||||
setw -g mode-keys vi
|
||||
|
||||
# Faster key repetition
|
||||
set -s escape-time 0
|
||||
|
||||
# Reload config without restarting
|
||||
bind r source-file ~/.tmux.conf \; display "Config reloaded"
|
||||
|
||||
# Intuitive splits: | and -
|
||||
bind | split-window -h -c "#{pane_current_path}"
|
||||
bind - split-window -v -c "#{pane_current_path}"
|
||||
|
||||
# New windows open in current directory
|
||||
bind c new-window -c "#{pane_current_path}"
|
||||
|
||||
# Status bar
|
||||
set -g status-right "#{session_name} | %H:%M %d-%b"
|
||||
set -g status-interval 5
|
||||
```
|
||||
|
||||
### Copy Mode and Scrollback
|
||||
|
||||
```bash
|
||||
# Enter copy mode (scroll up through output)
|
||||
# Prefix + [
|
||||
|
||||
# In vi mode:
|
||||
# / to search forward, ? to search backward
|
||||
# Space to start selection, Enter to copy
|
||||
# q to exit copy mode
|
||||
|
||||
# Paste the most recent buffer
|
||||
# Prefix + ]
|
||||
|
||||
# List paste buffers
|
||||
tmux list-buffers
|
||||
|
||||
# Show the most recent buffer
|
||||
tmux show-buffer
|
||||
|
||||
# Save buffer to a file
|
||||
tmux save-buffer /tmp/tmux-output.txt
|
||||
|
||||
# Load a file into a buffer
|
||||
tmux load-buffer /tmp/data.txt
|
||||
|
||||
# Pipe pane output to a command
|
||||
tmux pipe-pane -t work:1.0 "cat >> ~/session.log"
|
||||
```
|
||||
|
||||
### Practical Automation Patterns
|
||||
|
||||
```bash
|
||||
# Idempotent session: create or attach
|
||||
ensure_session() {
|
||||
local name="$1"
|
||||
tmux has-session -t "$name" 2>/dev/null \
|
||||
|| tmux new-session -d -s "$name"
|
||||
tmux attach -t "$name"
|
||||
}
|
||||
|
||||
# Run a command in a new background window and tail its output
|
||||
run_bg() {
|
||||
local session="${1:-main}" cmd="${*:2}"
|
||||
tmux new-window -t "$session" -n "bg-$$"
|
||||
tmux send-keys -t "$session:bg-$$" "$cmd" Enter
|
||||
}
|
||||
|
||||
# Wait for a pane to produce specific output (polling)
|
||||
wait_for_output() {
|
||||
local target="$1" pattern="$2" timeout="${3:-30}"
|
||||
local elapsed=0
|
||||
while (( elapsed < timeout )); do
|
||||
tmux capture-pane -t "$target" -p | grep -q "$pattern" && return 0
|
||||
sleep 1
|
||||
(( elapsed++ ))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Kill all background windows matching a name prefix
|
||||
kill_bg_windows() {
|
||||
local session="$1" prefix="${2:-bg-}"
|
||||
tmux list-windows -t "$session" -F "#W" \
|
||||
| grep "^${prefix}" \
|
||||
| while read -r win; do
|
||||
tmux kill-window -t "${session}:${win}"
|
||||
done
|
||||
}
|
||||
```
|
||||
|
||||
### Remote and SSH Workflows
|
||||
|
||||
```bash
|
||||
# SSH and immediately attach to an existing session
|
||||
ssh user@host -t "tmux attach -t work || tmux new-session -s work"
|
||||
|
||||
# Run a command on remote host inside a tmux session (fire and forget)
|
||||
ssh user@host "tmux new-session -d -s deploy 'bash /opt/deploy.sh'"
|
||||
|
||||
# Watch the remote session output from another terminal
|
||||
ssh user@host -t "tmux attach -t deploy -r" # read-only attach
|
||||
|
||||
# Pair programming: share a session (both users attach to the same session)
|
||||
# User 1:
|
||||
tmux new-session -s shared
|
||||
# User 2 (same server):
|
||||
tmux attach -t shared
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Always name sessions (`-s name`) in scripts — unnamed sessions are hard to target reliably
|
||||
- Use `tmux has-session -t name 2>/dev/null` before creating to make scripts idempotent
|
||||
- Set `-x` and `-y` when creating detached sessions to give panes a proper size for commands that check terminal dimensions
|
||||
- Use `send-keys ... Enter` for automation rather than piping stdin — it works even when the target pane is running an interactive program
|
||||
- Keep `~/.tmux.conf` in version control for reproducibility across machines
|
||||
- Prefer `bind -n` for bindings that don't need the prefix, but only for keys that don't conflict with application shortcuts
|
||||
|
||||
## Security & Safety Notes
|
||||
|
||||
- `send-keys` executes commands in a pane without confirmation — verify the target (`-t session:window.pane`) before use in scripts to avoid sending keystrokes to the wrong pane
|
||||
- Read-only attach (`-r`) is appropriate when sharing sessions with others to prevent accidental input
|
||||
- Avoid storing secrets in tmux window/pane titles or environment variables exported into sessions on shared machines
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Problem:** `tmux` commands from a script fail with "no server running"
|
||||
**Solution:** Start the server first with `tmux start-server`, or create a detached session before running other commands.
|
||||
|
||||
- **Problem:** Pane size is 0x0 when creating a detached session
|
||||
**Solution:** Pass explicit dimensions: `tmux new-session -d -s name -x 200 -y 50`.
|
||||
|
||||
- **Problem:** `send-keys` types the text but doesn't run the command
|
||||
**Solution:** Ensure you pass `Enter` (capital E) as a second argument: `tmux send-keys -t target "cmd" Enter`.
|
||||
|
||||
- **Problem:** Script creates a duplicate session each run
|
||||
**Solution:** Guard with `tmux has-session -t name 2>/dev/null || tmux new-session -d -s name`.
|
||||
|
||||
- **Problem:** Copy-mode selection doesn't work as expected
|
||||
**Solution:** Confirm `mode-keys vi` or `mode-keys emacs` is set to match your preference in `~/.tmux.conf`.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `@bash-pro` — Writing the shell scripts that orchestrate tmux sessions
|
||||
- `@bash-linux` — General Linux terminal patterns used inside tmux panes
|
||||
- `@ssh` — Combining tmux with SSH for persistent remote workflows
|
||||
Reference in New Issue
Block a user