Last active
June 12, 2026 13:08
-
-
Save gitawego/ed6c86ca0202879b0e1966696b020788 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Claude Code statusline: cache-hit tracking merged with | |
| # https://github.com/danielmackay/claude-code-statusline | |
| # | |
| # Line 1: 🤖 model | 🧠 ctx N% | 💪 effort | ⚡ Cache N% ΣN% MM:SS | 💰 $X.XX | |
| # Cache: last-response hit rate, Σ session-cumulative rate, TTL countdown. | |
| # ↺ (bold red) marks a cache bust — large rebuild after prefix invalidation. | |
| # Line 2: 📁 dir | 🌳 worktree | 🌿 branch +staged ~modified | ⏱️ 5h ███░░░ N% ~dur | 7d ███░░░ N% ~dur | |
| # | |
| # State dir: ~/.claude/statusline-state/<session_hash>.json (cache TTL tracking) | |
| # | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # MANUAL CONFIGURATION | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # To wire this script up as your Claude Code statusline, add a `statusLine` | |
| # block to ~/.claude/settings.json (user-level) or .claude/settings.json | |
| # (project-level). Claude Code pipes a JSON payload to stdin on every render | |
| # and displays whatever this script prints to stdout. | |
| # | |
| # 1. Make the script executable: | |
| # | |
| # chmod +x .ps-digital-genai/scripts/statusline-command.sh | |
| # | |
| # 2. Add this block to ~/.claude/settings.json: | |
| # | |
| # { | |
| # "statusLine": { | |
| # "type": "command", | |
| # "command": "/absolute/path/to/.ps-digital-genai/scripts/statusline-command.sh", | |
| # "padding": 0 | |
| # } | |
| # } | |
| # | |
| # Use an absolute path — Claude Code does not expand `~` or resolve | |
| # relative paths for statusline commands. For project-local use, point at | |
| # the script inside the project (e.g. | |
| # "$PWD/.ps-digital-genai/scripts/statusline-command.sh"). | |
| # | |
| # 3. Reload Claude Code (restart the session or run `/config`). The two-line | |
| # statusline will appear at the bottom of the prompt: | |
| # | |
| # 🤖 opus[1m] | 🧠 ctx 12% | 💪 high | ⚡ Cache 97% 59:43 | 💰 $8.83 | |
| # 📁 myrepo | 🌿 feature/petinsurance ~5 | ⏱️ 5h ███░░░ 23% ~3h12m | 7d ██░░░░ 41% ~4d6h | |
| # | |
| # Requirements: `jq`, `awk`, `git`, `md5sum`, `date` (all standard on macOS | |
| # and most Linux distros). The script silently no-ops if input JSON is empty. | |
| # | |
| # To disable temporarily, comment out or remove the `statusLine` block from | |
| # settings.json — no need to delete this file. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| STATE_DIR="$HOME/.claude/statusline-state" | |
| mkdir -p "$STATE_DIR" | |
| # Read JSON input once | |
| INPUT=$(cat) | |
| # One-shot diagnostic: dump the latest statusline JSON for inspection. | |
| # Overwrites the file on every render so it's never large. | |
| # Write-then-rename so concurrent readers never see a truncated file | |
| { echo "$INPUT" | jq '.' > "/tmp/statusline-last.json.$$" 2>/dev/null \ | |
| && mv -f "/tmp/statusline-last.json.$$" /tmp/statusline-last.json; } || rm -f "/tmp/statusline-last.json.$$" | |
| SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') | |
| # Derive a short hash from session_id for the filename | |
| if [ -n "$SESSION_ID" ]; then | |
| SESSION_HASH=$(echo -n "$SESSION_ID" | md5sum | cut -c1-12) | |
| else | |
| SESSION_HASH="default" | |
| fi | |
| STATE_FILE="$STATE_DIR/${SESSION_HASH}.json" | |
| # ------------------------------------------------------------------ | |
| # Parse current_usage tokens | |
| # ------------------------------------------------------------------ | |
| USAGE=$(echo "$INPUT" | jq -r '.context_window.current_usage // empty') | |
| if [ -n "$USAGE" ]; then | |
| INPUT_TOKENS=$(echo "$USAGE" | jq -r '.input_tokens // 0') | |
| CACHE_CREATE=$(echo "$USAGE" | jq -r '.cache_creation_input_tokens // 0') | |
| CACHE_READ=$(echo "$USAGE" | jq -r '.cache_read_input_tokens // 0') | |
| else | |
| INPUT_TOKENS=0 | |
| CACHE_CREATE=0 | |
| CACHE_READ=0 | |
| fi | |
| # Signature to detect a new response: concatenate the three token counts | |
| SIGNATURE="${INPUT_TOKENS}:${CACHE_CREATE}:${CACHE_READ}" | |
| # ------------------------------------------------------------------ | |
| # Load existing state (validate before use) | |
| # ------------------------------------------------------------------ | |
| STORED_SIG="" | |
| STORED_TS=0 | |
| STORED_HIT_RATE="" | |
| STORED_TOT_READ=0 | |
| STORED_TOT_PROMPT=0 | |
| if [ -f "$STATE_FILE" ]; then | |
| # Validate: must be a JSON object with the expected keys | |
| VALID=$(jq -e 'type == "object" and has("sig") and has("ts") and has("hit_rate")' "$STATE_FILE" 2>/dev/null) | |
| if [ "$VALID" = "true" ]; then | |
| STORED_SIG=$(jq -r '.sig' "$STATE_FILE") | |
| STORED_TS=$(jq -r '.ts' "$STATE_FILE") | |
| STORED_HIT_RATE=$(jq -r '.hit_rate' "$STATE_FILE") | |
| # Session-cumulative token totals (absent in old-format state files) | |
| STORED_TOT_READ=$(jq -r '.tot_read // 0' "$STATE_FILE") | |
| STORED_TOT_PROMPT=$(jq -r '.tot_prompt // 0' "$STATE_FILE") | |
| fi | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Compute cache hit rate | |
| # ------------------------------------------------------------------ | |
| TOTAL=$(( INPUT_TOKENS + CACHE_CREATE + CACHE_READ )) | |
| if [ "$TOTAL" -gt 0 ] && [ -n "$USAGE" ]; then | |
| # Use awk for floating-point; round to integer | |
| HIT_RATE=$(awk "BEGIN { printf \"%d\", ($CACHE_READ / $TOTAL) * 100 }") | |
| elif [ -n "$STORED_HIT_RATE" ]; then | |
| # No current_usage yet — fall back to last known hit rate | |
| HIT_RATE="$STORED_HIT_RATE" | |
| else | |
| HIT_RATE="" | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Update state only when signature changes (new response) | |
| # ------------------------------------------------------------------ | |
| NOW=$(date +%s) | |
| TOT_READ=$STORED_TOT_READ | |
| TOT_PROMPT=$STORED_TOT_PROMPT | |
| if [ -n "$USAGE" ] && [ "$SIGNATURE" != "$STORED_SIG" ]; then | |
| # New response detected — accumulate session totals and update state | |
| TOT_READ=$(( STORED_TOT_READ + CACHE_READ )) | |
| TOT_PROMPT=$(( STORED_TOT_PROMPT + TOTAL )) | |
| jq -n \ | |
| --arg sig "$SIGNATURE" \ | |
| --argjson ts "$NOW" \ | |
| --arg hit_rate "${HIT_RATE:-}" \ | |
| --argjson tot_read "$TOT_READ" \ | |
| --argjson tot_prompt "$TOT_PROMPT" \ | |
| '{"sig": $sig, "ts": $ts, "hit_rate": $hit_rate, | |
| "tot_read": $tot_read, "tot_prompt": $tot_prompt}' \ | |
| > "$STATE_FILE" | |
| STORED_TS=$NOW | |
| fi | |
| # Session-cumulative hit rate (all responses seen by this statusline) | |
| CUM_RATE="" | |
| if [ "$TOT_PROMPT" -gt 0 ] 2>/dev/null; then | |
| CUM_RATE=$(awk "BEGIN { printf \"%d\", ($TOT_READ / $TOT_PROMPT) * 100 }") | |
| fi | |
| # Use persisted timestamp if we didn't just update | |
| if [ "$STORED_TS" -gt 0 ] && [ "$SIGNATURE" = "$STORED_SIG" ]; then | |
| REF_TS=$STORED_TS | |
| else | |
| REF_TS=$NOW | |
| fi | |
| # ------------------------------------------------------------------ | |
| # TTL countdown: 1 hour from last response | |
| # ------------------------------------------------------------------ | |
| TTL=3600 | |
| ELAPSED=$(( NOW - REF_TS )) | |
| REMAINING=$(( TTL - ELAPSED )) | |
| if [ "$REMAINING" -le 0 ]; then | |
| TTL_STR="exp" | |
| TTL_COLOR="\033[2;37m" # dim grey — expired | |
| else | |
| MINS=$(( REMAINING / 60 )) | |
| SECS=$(( REMAINING % 60 )) | |
| TTL_STR=$(printf "%d:%02d" "$MINS" "$SECS") | |
| if [ "$REMAINING" -ge 2400 ]; then | |
| # 40-60 min remaining: green | |
| TTL_COLOR="\033[0;32m" | |
| elif [ "$REMAINING" -ge 1200 ]; then | |
| # 20-40 min remaining: yellow | |
| TTL_COLOR="\033[0;33m" | |
| elif [ "$REMAINING" -ge 300 ]; then | |
| # 5-20 min remaining: red | |
| TTL_COLOR="\033[0;31m" | |
| else | |
| # Last 5 min: bold red (terminal will blink if supported) | |
| TTL_COLOR="\033[1;31m" | |
| fi | |
| fi | |
| GREEN="\033[0;32m" | |
| YELLOW="\033[0;33m" | |
| RED="\033[0;31m" | |
| RESET="\033[0m" | |
| # ------------------------------------------------------------------ | |
| # Cache hit rate color: green >=50%, grey <50%. | |
| # Bust detection: a near-zero hit rate combined with a large cache write | |
| # means the prompt prefix was invalidated (compaction, model switch, edited | |
| # context) and rebuilt at 1.25x cost — flag it in bold red with ↺. | |
| # ------------------------------------------------------------------ | |
| CACHE_BUST="" | |
| if [ -n "$USAGE" ] && [ "$CACHE_CREATE" -ge 20000 ] 2>/dev/null \ | |
| && [ -n "$HIT_RATE" ] && [ "$HIT_RATE" -lt 20 ] 2>/dev/null; then | |
| CACHE_BUST="↺" | |
| fi | |
| if [ -n "$CACHE_BUST" ]; then | |
| CACHE_COLOR="\033[1;31m" # bold red — paid cache rebuild | |
| elif [ -n "$HIT_RATE" ] && [ "$HIT_RATE" -ge 50 ] 2>/dev/null; then | |
| CACHE_COLOR="$GREEN" | |
| else | |
| CACHE_COLOR="\033[2;37m" # dim grey | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Context window usage segment (computed live, not persisted) | |
| # ------------------------------------------------------------------ | |
| CTX_STR="" | |
| CTX_COLOR="" | |
| if [ -n "$USAGE" ]; then | |
| # Prefer the percentage Claude Code computes itself (accounts for output | |
| # tokens and the session's actual window size, e.g. 1M models). | |
| CTX_PCT=$(echo "$INPUT" | jq -r '.context_window.used_percentage // empty' 2>/dev/null) | |
| CTX_PCT=${CTX_PCT%.*} | |
| if [ -z "$CTX_PCT" ] || [ "$CTX_PCT" = "null" ]; then | |
| # Fallback: compute from token counts against the window size | |
| CTX_WINDOW=$(echo "$INPUT" | jq -r ' | |
| .context_window.context_window_size // | |
| .context_window.max_tokens // | |
| .context_window.context_limit // | |
| .model.context_window // | |
| empty' 2>/dev/null) | |
| # Fall back to 200000; use 1000000 for 1M models | |
| if [ -z "$CTX_WINDOW" ] || [ "$CTX_WINDOW" = "null" ]; then | |
| MODEL_ID=$(echo "$INPUT" | jq -r '.model.id // empty') | |
| MODEL_NAME=$(echo "$INPUT" | jq -r '.model.display_name // empty') | |
| if echo "$MODEL_ID$MODEL_NAME" | grep -qiE '1m'; then | |
| CTX_WINDOW=1000000 | |
| else | |
| CTX_WINDOW=200000 | |
| fi | |
| fi | |
| # Numerator: all tokens in current_usage (output tokens not included — that's correct) | |
| CTX_USED=$(( INPUT_TOKENS + CACHE_CREATE + CACHE_READ )) | |
| CTX_PCT=$(awk "BEGIN { printf \"%d\", ($CTX_USED / $CTX_WINDOW) * 100 }") | |
| fi | |
| if [ "$CTX_PCT" -ge 95 ]; then | |
| CTX_COLOR="\033[1;31m" # bold red | |
| elif [ "$CTX_PCT" -ge 80 ]; then | |
| CTX_COLOR="$RED" | |
| elif [ "$CTX_PCT" -ge 50 ]; then | |
| CTX_COLOR="$YELLOW" | |
| else | |
| CTX_COLOR="$GREEN" | |
| fi | |
| CTX_STR="${CTX_COLOR}ctx ${CTX_PCT}%${RESET}" | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Model segment | |
| # ------------------------------------------------------------------ | |
| # Read settings.json model value early — used for [1m] detection below | |
| _SETTINGS_MODEL=$(jq -r '.model // empty' "$HOME/.claude/settings.json" 2>/dev/null) | |
| MODEL_STR=$(echo "$INPUT" | jq -r '.model.display_name // .model.id // empty' 2>/dev/null) | |
| if [ -z "$MODEL_STR" ] || [ "$MODEL_STR" = "null" ]; then | |
| MODEL_STR="$_SETTINGS_MODEL" | |
| fi | |
| # Compact model name: "Claude Opus 4.7 (1M context)" → "opus[1m]" | |
| if [ -n "$MODEL_STR" ] && [ "$MODEL_STR" != "null" ]; then | |
| # Detect [1m] from the runtime context window only — reflect the actual | |
| # allocation, not the requested model in settings.json. | |
| _has_1m=0 | |
| _CTX_SIZE=$(echo "$INPUT" | jq -r '.context_window.context_window_size // empty' 2>/dev/null) | |
| if [ -n "$_CTX_SIZE" ] && [ "$_CTX_SIZE" != "null" ] && [ "$_CTX_SIZE" -ge 1000000 ] 2>/dev/null; then | |
| _has_1m=1 | |
| fi | |
| _compact=$(echo "$MODEL_STR" \ | |
| | tr '[:upper:]' '[:lower:]' \ | |
| | sed -E 's/^claude //' \ | |
| | sed -E 's/ ?\(1m context\)/[1m]/g' \ | |
| | sed -E 's/ [0-9]+\.[0-9]+//' \ | |
| | sed -E 's/^[[:space:]]+|[[:space:]]+$//') | |
| # If result still has hyphens (id form), extract family name | |
| if echo "$_compact" | grep -q '-'; then | |
| _family=$(echo "$_compact" | grep -oE 'opus|sonnet|haiku' | head -1) | |
| if [ -n "$_family" ]; then | |
| _compact="$_family" | |
| [ "$_has_1m" -eq 1 ] && _compact="${_compact}[1m]" | |
| fi | |
| fi | |
| # Force [1m] suffix when the user is on the 1M context variant, | |
| # even if Claude Code's display_name didn't include it | |
| if [ "$_has_1m" -eq 1 ] && ! echo "$_compact" | grep -q '\[1m\]'; then | |
| _compact="${_compact}[1m]" | |
| fi | |
| # Fall back to raw id if compact is empty | |
| if [ -z "$_compact" ]; then | |
| _compact=$(echo "$INPUT" | jq -r '.model.id // empty' 2>/dev/null) | |
| fi | |
| MODEL_STR="$_compact" | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Effort level segment (red) | |
| # ------------------------------------------------------------------ | |
| EFFORT_STR=$(echo "$INPUT" | jq -r '.effort.level // .output_style.name // empty' 2>/dev/null) | |
| if [ -z "$EFFORT_STR" ] || [ "$EFFORT_STR" = "null" ]; then | |
| EFFORT_STR=$(jq -r '.effortLevel // empty' "$HOME/.claude/settings.json" 2>/dev/null) | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Cost segment | |
| # ------------------------------------------------------------------ | |
| COST_STR="" | |
| TOTAL_COST=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // empty' 2>/dev/null) | |
| if [ -n "$TOTAL_COST" ] && [ "$TOTAL_COST" != "null" ]; then | |
| COST_STR=$(awk "BEGIN { printf \"\$%.2f\", $TOTAL_COST }") | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Directory / worktree / git segments (line 2) | |
| # ------------------------------------------------------------------ | |
| CWD=$(echo "$INPUT" | jq -r '.cwd // .workspace.current_dir // empty' 2>/dev/null) | |
| DIR_STR="" | |
| if [ -n "$CWD" ] && [ "$CWD" != "null" ]; then | |
| REPO_ROOT=$(git -C "$CWD" rev-parse --show-toplevel 2>/dev/null || echo "$CWD") | |
| DIR_STR=$(basename "$REPO_ROOT") | |
| fi | |
| WORKTREE_STR=$(echo "$INPUT" | jq -r '.worktree.name // empty' 2>/dev/null) | |
| [ "$WORKTREE_STR" = "null" ] && WORKTREE_STR="" | |
| BRANCH_STR="" | |
| if [ -n "$CWD" ] && [ "$CWD" != "null" ]; then | |
| BRANCH_STR=$(git -C "$CWD" symbolic-ref --short HEAD 2>/dev/null) | |
| if [ -z "$BRANCH_STR" ]; then | |
| BRANCH_STR=$(git -C "$CWD" rev-parse --short HEAD 2>/dev/null) | |
| fi | |
| # Staged / modified file counts: +N (green), ~N (yellow) | |
| if [ -n "$BRANCH_STR" ]; then | |
| STAGED=$(git -C "$CWD" diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ') | |
| MODIFIED=$(git -C "$CWD" diff --numstat 2>/dev/null | wc -l | tr -d ' ') | |
| [ "$STAGED" -gt 0 ] 2>/dev/null && BRANCH_STR="${BRANCH_STR} ${GREEN}+${STAGED}${RESET}" | |
| [ "$MODIFIED" -gt 0 ] 2>/dev/null && BRANCH_STR="${BRANCH_STR} ${YELLOW}~${MODIFIED}${RESET}" | |
| fi | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Rate limit segments (line 3) | |
| # ------------------------------------------------------------------ | |
| FIVE_H_PCT=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.used_percentage // empty' 2>/dev/null) | |
| FIVE_H_RESET=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.resets_at // empty' 2>/dev/null) | |
| SEVEN_D_PCT=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.used_percentage // empty' 2>/dev/null) | |
| SEVEN_D_RESET=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.resets_at // empty' 2>/dev/null) | |
| # Format a duration from a reset epoch: "~XhYYm" or "~XdYh" | |
| format_dur_hours() { | |
| local reset_ts="$1" | |
| local secs=$(( reset_ts - NOW )) | |
| [ "$secs" -le 0 ] && echo "~0m" && return | |
| printf "~%dh%02dm" $(( secs / 3600 )) $(( (secs % 3600) / 60 )) | |
| } | |
| format_dur_days() { | |
| local reset_ts="$1" | |
| local secs=$(( reset_ts - NOW )) | |
| [ "$secs" -le 0 ] && echo "~0d" && return | |
| printf "~%dd%dh" $(( secs / 86400 )) $(( (secs % 86400) / 3600 )) | |
| } | |
| # 6-char progress bar: ███░░░ | |
| make_bar() { | |
| local pct="$1" width=6 bar="" i=0 | |
| [ "$pct" -gt 100 ] 2>/dev/null && pct=100 | |
| local filled=$(( pct * width / 100 )) | |
| while [ $i -lt $filled ]; do bar="${bar}█"; i=$(( i + 1 )); done | |
| while [ $i -lt $width ]; do bar="${bar}░"; i=$(( i + 1 )); done | |
| printf "%s" "$bar" | |
| } | |
| # label, pct, reset_ts, dur_formatter -> colored "label ██████ N% ~dur" | |
| format_rl() { | |
| local label="$1" pct="$2" reset_ts="$3" dur_fn="$4" | |
| [ -z "$pct" ] || [ "$pct" = "null" ] && return | |
| pct=$(printf "%.0f" "$pct" 2>/dev/null) || return | |
| local color="$GREEN" | |
| if [ "$pct" -ge 90 ] 2>/dev/null; then color="$RED" | |
| elif [ "$pct" -ge 70 ] 2>/dev/null; then color="$YELLOW" | |
| fi | |
| local dur="" | |
| if [ -n "$reset_ts" ] && [ "$reset_ts" != "null" ]; then | |
| dur=" $($dur_fn "$reset_ts")" | |
| fi | |
| printf "%b%s %s %s%%%s%b" "$color" "$label" "$(make_bar "$pct")" "$pct" "$dur" "$RESET" | |
| } | |
| FIVE_H_SEG=$(format_rl "5h" "$FIVE_H_PCT" "$FIVE_H_RESET" format_dur_hours) | |
| SEVEN_D_SEG=$(format_rl "7d" "$SEVEN_D_PCT" "$SEVEN_D_RESET" format_dur_days) | |
| # ------------------------------------------------------------------ | |
| # Assemble lines | |
| # ------------------------------------------------------------------ | |
| join_segs() { | |
| local out="" seg | |
| for seg in "$@"; do | |
| [ -z "$seg" ] && continue | |
| if [ -n "$out" ]; then out="${out} | ${seg}"; else out="$seg"; fi | |
| done | |
| printf '%s' "$out" | |
| } | |
| CACHE_SEG="" | |
| if [ -n "$HIT_RATE" ]; then | |
| CACHE_SEG="${CACHE_COLOR}Cache ${HIT_RATE}%${CACHE_BUST}${RESET}" | |
| # Σ = session-cumulative rate | |
| if [ -n "$CUM_RATE" ]; then | |
| CACHE_SEG="${CACHE_SEG} \033[2;37mΣ${CUM_RATE}%${RESET}" | |
| fi | |
| CACHE_SEG="${CACHE_SEG} ${TTL_COLOR}${TTL_STR}${RESET}" | |
| fi | |
| EFFORT_SEG="" | |
| if [ -n "$EFFORT_STR" ] && [ "$EFFORT_STR" != "null" ]; then | |
| EFFORT_SEG="${RED}${EFFORT_STR}${RESET}" | |
| fi | |
| LINE1=$(join_segs \ | |
| "${MODEL_STR:+🤖 $MODEL_STR}" \ | |
| "${CTX_STR:+🧠 $CTX_STR}" \ | |
| "${EFFORT_SEG:+💪 $EFFORT_SEG}" \ | |
| "${CACHE_SEG:+⚡ $CACHE_SEG}" \ | |
| "${COST_STR:+💰 $COST_STR}") | |
| LINE2=$(join_segs \ | |
| "${DIR_STR:+📁 $DIR_STR}" \ | |
| "${WORKTREE_STR:+🌳 $WORKTREE_STR}" \ | |
| "${BRANCH_STR:+🌿 $BRANCH_STR}" \ | |
| "${FIVE_H_SEG:+⏱️ $FIVE_H_SEG}" \ | |
| "$SEVEN_D_SEG") | |
| # ------------------------------------------------------------------ | |
| # Final output (skip empty lines) | |
| # ------------------------------------------------------------------ | |
| OUT="" | |
| for line in "$LINE1" "$LINE2"; do | |
| [ -z "$line" ] && continue | |
| if [ -n "$OUT" ]; then OUT="${OUT}\n${line}"; else OUT="$line"; fi | |
| done | |
| [ -z "$OUT" ] && exit 0 | |
| printf '%b' "$OUT" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
prompt: