| 1 | #!/usr/bin/env bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | usage() { |
| 5 | cat <<'USAGE' |
| 6 | Usage: wait-for-text.sh -t target -p pattern [options] |
| 7 | |
| 8 | Poll a tmux pane for text and exit when found. |
| 9 | |
| 10 | Options: |
| 11 | -t, --target tmux target (session:window.pane), required |
| 12 | -p, --pattern regex pattern to look for, required |
| 13 | -F, --fixed treat pattern as a fixed string (grep -F) |
| 14 | -T, --timeout seconds to wait (integer, default: 15) |
| 15 | -i, --interval poll interval in seconds (default: 0.5) |
| 16 | -l, --lines number of history lines to inspect (integer, default: 1000) |
| 17 | -h, --help show this help |
| 18 | USAGE |
| 19 | } |
| 20 | |
| 21 | target="" |
| 22 | pattern="" |
| 23 | grep_flag="-E" |
| 24 | timeout=15 |
| 25 | interval=0.5 |
| 26 | lines=1000 |
| 27 | |
| 28 | while [[ $# -gt 0 ]]; do |
| 29 | case "$1" in |
| 30 | -t|--target) target="${2-}"; shift 2 ;; |
| 31 | -p|--pattern) pattern="${2-}"; shift 2 ;; |
| 32 | -F|--fixed) grep_flag="-F"; shift ;; |
| 33 | -T|--timeout) timeout="${2-}"; shift 2 ;; |
| 34 | -i|--interval) interval="${2-}"; shift 2 ;; |
| 35 | -l|--lines) lines="${2-}"; shift 2 ;; |
| 36 | -h|--help) usage; exit 0 ;; |
| 37 | *) echo "Unknown option: $1" >&2; usage; exit 1 ;; |
| 38 | esac |
| 39 | done |
| 40 | |
| 41 | if [[ -z "$target" || -z "$pattern" ]]; then |
| 42 | echo "target and pattern are required" >&2 |
| 43 | usage |
| 44 | exit 1 |
| 45 | fi |
| 46 | |
| 47 | if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then |
| 48 | echo "timeout must be an integer number of seconds" >&2 |
| 49 | exit 1 |
| 50 | fi |
| 51 | |
| 52 | if ! [[ "$lines" =~ ^[0-9]+$ ]]; then |
| 53 | echo "lines must be an integer" >&2 |
| 54 | exit 1 |
| 55 | fi |
| 56 | |
| 57 | if ! command -v tmux >/dev/null 2>&1; then |
| 58 | echo "tmux not found in PATH" >&2 |
| 59 | exit 1 |
| 60 | fi |
| 61 | |
| 62 | # End time in epoch seconds (integer, good enough for polling) |
| 63 | start_epoch=$(date +%s) |
| 64 | deadline=$((start_epoch + timeout)) |
| 65 | |
| 66 | while true; do |
| 67 | # -J joins wrapped lines, -S uses negative index to read last N lines |
| 68 | pane_text="$(tmux capture-pane -p -J -t "$target" -S "-${lines}" 2>/dev/null || true)" |
| 69 | |
| 70 | if printf '%s\n' "$pane_text" | grep $grep_flag -- "$pattern" >/dev/null 2>&1; then |
| 71 | exit 0 |
| 72 | fi |
| 73 | |
| 74 | now=$(date +%s) |
| 75 | if (( now >= deadline )); then |
| 76 | echo "Timed out after ${timeout}s waiting for pattern: $pattern" >&2 |
| 77 | echo "Last ${lines} lines from $target:" >&2 |
| 78 | printf '%s\n' "$pane_text" >&2 |
| 79 | exit 1 |
| 80 | fi |
| 81 | |
| 82 | sleep "$interval" |
| 83 | done |
| 84 |