command-prompt

Installation
SKILL.md

Command Prompt: Shell Scripting and Configuration

Reference skill for writing commands, scripts, and configuration across Unix shells. Detects the target shell from context and routes to the appropriate reference.

Target versions (May 2026):

  • Zsh: 5.10
  • Bash: 5.3
  • Fish: 4.6
  • Nushell: 0.111
  • Tcsh: 6.24
  • Dash: 0.5.13

When to use

  • Writing shell commands, scripts, or one-liners
  • Configuring dotfiles (.zshrc, .bashrc, .profile, config.fish)
  • Writing completions, shell functions, or aliases
  • Porting scripts between shells
  • Debugging shell-specific behavior (globbing, arrays, expansion, quoting)
  • Setting up oh-my-zsh, starship, p10k, or other shell frameworks
  • Choosing which shell to target for a new script
  • Writing interactive commands on the user's local machine (zsh)

When NOT to use

  • Remote FreeBSD/OPNsense/pfSense commands - use firewall-appliance (handles tcsh/csh in the BSD context)
  • Ansible shell/command modules - use ansible (module gotchas differ from raw shell)
  • CI/CD pipeline shell blocks - use ci-cd (restricted environments, no interactive features)
  • General Linux sysadmin that isn't shell-specific - just do the task directly

AI Self-Check

Before returning any generated shell script or command, verify:

  • Shebang matches the detected target shell (not assumed bash)
  • set -euo pipefail (bash/zsh) or set -eu (POSIX sh) present in scripts
  • All variables double-quoted ("$var") unless word splitting is intentional
  • No shell-isms from the wrong shell (no [[ ]] in #!/bin/sh, no BASH_SOURCE in zsh)
  • Array indexing correct for the target shell (bash: 0-indexed, zsh: 1-indexed)
  • printf used over echo for non-trivial output
  • Glob safety guards in place (empty-glob case handled)
  • No hardcoded paths for tools (/usr/bin/git) - use command -v or bare command names
  • Temp files use mktemp with cleanup traps, not hardcoded /tmp/foo
  • No secrets in command history (use read -s or environment variables)

  • Current source checked: dated versions, CLI flags, API names, and support windows are verified against primary docs before repeating them
  • Hidden state identified: local config, credentials, caches, contexts, branches, cluster targets, or previous runs are made explicit before acting
  • Verification is real: final checks exercise the actual runtime, parser, service, or integration point instead of only linting prose or happy paths
  • Shell identified: examples match POSIX sh, Bash, Zsh, or Fish semantics intentionally
  • Quoting tested: paths with spaces, empty variables, and glob characters behave safely

Performance

  • Use builtins and stream processing for large inputs; avoid command substitution that buffers entire files.
  • Prefer rg, fd, and targeted file lists when available, with portable fallbacks noted.
  • Avoid spawning subshells inside tight loops when xargs, arrays, or shell builtins fit.

Best Practices

  • Default to set -euo pipefail only when the script is written to handle those semantics.
  • Use -- before user-controlled paths for commands that support it.
  • Preview destructive expansions before rm, mv, chmod, chown, or recursive edits.

Workflow

Step 1: Detect the target shell

Before writing any shell code, determine the target shell. Check these signals in order:

Signal How to check Routes to
Shebang First line of existing script #!/usr/bin/env zsh -> zsh, #!/usr/bin/env bash -> bash, #!/bin/sh -> posix-sh
File name/extension .zsh, .zshrc, .zprofile, .zshenv -> zsh; .bash, .bashrc, .bash_profile -> bash; .fish, config.fish -> fish
User's shell Conversation context, $SHELL User's local machine = zsh
Task type What the script does See routing below

Task-based routing

Task Target shell Why
Interactive commands on user's machine zsh User's default shell
Portable scripts (new) bash Widest deployment, good feature set
Docker/CI containers bash or sh Containers often lack zsh
Minimal Alpine/BusyBox scripts POSIX sh Only ash/dash available
BSD system administration tcsh FreeBSD default (but see firewall-appliance skill)
Cross-shell startup (env vars, PATH) POSIX sh .profile sourced by all POSIX shells
Maximum portability requirement POSIX sh Only standard guaranteed on all Unixes

Step 2: Load the right reference

Target shell Reference file
Zsh references/zsh.md (~680 lines, 14 sections)
Bash references/bash.md (~710 lines, 13 sections)
POSIX sh references/posix-sh.md (~490 lines, 10 sections)
Fish, tcsh, nushell, others references/alt-shells.md (~420 lines, 4 shells)

Don't load all references. Pick the one that matches. If porting between two shells, load both.

Step 3: Write code, then verify

Use the cross-shell comparison below for quick lookups. After writing, run through the Verification Checklist at the bottom of this section.


Quick Cross-Shell Comparison

Feature POSIX sh Bash Zsh Fish
Arrays no (use $@) 0-indexed 1-indexed lists (1-indexed)
Assoc arrays no declare -A (4.0+) typeset -A no
Glob **/ no shopt -s globstar built-in built-in
Failed glob passes literal passes literal error no match
[[ ]] no yes yes no (use test)
Process sub <() no yes yes + =() (command | psub)
Word splitting on unquoted $var on unquoted $var no no
Arithmetic $(( )) only $(( )), (( )), let $(( )), (( )) math
String lowercase - ${var,,} ${var:l} string lower
Completions none basic (bash-completion) powerful (compsys) powerful (built-in)
Config file .profile .bashrc .zshrc config.fish
Shebang #!/bin/sh #!/usr/bin/env bash #!/usr/bin/env zsh #!/usr/bin/env fish
Script safety set -eu set -euo pipefail set -euo pipefail N/A (strict by default)
Non-forking cmd sub no ${ cmd; } (5.3+) ${ cmd } (5.10+) no

Universal Patterns (All POSIX Shells)

These work in sh, bash, and zsh. Fish has different syntax for most of these - see the alt-shells reference.

Piping and redirection

Pattern Effect
cmd1 | cmd2 Pipe stdout of cmd1 to stdin of cmd2
cmd > file Redirect stdout to file (overwrite)
cmd >> file Redirect stdout to file (append)
cmd 2> file Redirect stderr to file
cmd &> file Redirect both stdout and stderr (bash/zsh, not POSIX)
cmd 2>&1 Redirect stderr to stdout
cmd > /dev/null 2>&1 Silence all output (POSIX-portable)
cmd < file Feed file as stdin
cmd <<'EOF' Here document (single-quoted delimiter = no expansion)
cmd <<< "string" Here string (bash/zsh, not POSIX)
cmd1 | tee file | cmd2 Send stdout to both file and cmd2

Chaining

Pattern Behavior
cmd1 ; cmd2 Run sequentially, ignore exit codes
cmd1 && cmd2 Run cmd2 only if cmd1 succeeds (exit 0)
cmd1 || cmd2 Run cmd2 only if cmd1 fails (exit non-0)
cmd & Run in background
cmd1 && cmd2 || cmd3 Poor man's if/else (not reliable - cmd3 runs if cmd2 fails too)

Job control

Command Effect
Ctrl+Z Suspend foreground job
bg / bg %N Resume job in background
fg / fg %N Resume job in foreground
jobs List background jobs
kill %N Kill job by number
wait Wait for all background jobs
wait $PID Wait for specific PID
disown %N Detach job from shell (survives logout)

Signals and traps

# Cleanup on exit (works in sh, bash, zsh)
cleanup() {
    rm -f "$tmpfile"
}
trap cleanup EXIT INT TERM

# Ignore a signal
trap '' HUP

# Common signals: EXIT (0), HUP (1), INT (2), TERM (15), USR1 (10), USR2 (12)

# Graceful kill with SIGTERM -> wait -> SIGKILL escalation
kill_gracefully() {
    local pid=$1 timeout=${2:-5}
    kill -TERM "$pid" 2>/dev/null || return
    local i=0
    while kill -0 "$pid" 2>/dev/null && [ $i -lt $timeout ]; do
        sleep 1; i=$((i+1))
    done
    kill -0 "$pid" 2>/dev/null && kill -KILL "$pid"
}

Interactive "kill by name" (zsh) - covers search, space-safe names, confirm, TERM->KILL escalation:

pk() {                                           # usage: pk <pattern>
    local pattern=$1 pids
    pids=(${(f)"$(pgrep -af -- "$pattern")"})    # -f matches full cmdline (spaces ok)
    (( $#pids )) || { print -u2 "no match"; return 1 }
    printf '%s\n' "${pids[@]}"                   # show PID + cmdline
    read -q "?kill these? [y/N] " || { print; return 1 }
    print
    for line in $pids; do kill_gracefully ${line%% *} 3; done
}

Quoting rules

Syntax Expansion Use for
"double" $var, $(cmd), ${param} expand; \ escapes Most strings with variables
'single' Nothing expands, completely literal Regexes, JSON, strings with $ or !
$'ansi' \n, \t, \' interpreted (bash/zsh) Strings needing literal control chars
\char Escapes one character Single special chars in unquoted context

Golden rule: when in doubt, double-quote. "$var" is almost always correct. Unquoted $var causes word splitting (in sh/bash) or glob expansion.

Exit codes

Code Meaning
0 Success
1 General error
2 Misuse of shell builtin
126 Command found but not executable
127 Command not found
128+N Killed by signal N (e.g., 130 = Ctrl+C / SIGINT)

Common portable idioms

# Check if command exists
command -v git >/dev/null 2>&1 || { echo "git required" >&2; exit 1; }

# Default variable value
: "${VAR:=default}"       # set VAR to "default" if unset or empty
name="${1:-anonymous}"     # parameter default

# Temporary file (portable)
tmpfile=$(mktemp) || exit 1
trap 'rm -f "$tmpfile"' EXIT

# Read file line by line
while IFS= read -r line; do
    printf '%s\n' "$line"
done < file.txt

# Loop over glob results
for f in *.txt; do
    [ -e "$f" ] || continue    # guard against no matches (POSIX sh)
    echo "$f"
done

Completions Quick Reference (Zsh)

Zsh's completion system (compsys) handles subcommand routing natively. Minimal working example for a CLI tool with subcommands:

#compdef mycli

_mycli() {
  local -a subcmds=(
    'init:Initialize a new project'
    'build:Build the project'
    'deploy:Deploy to target environment'
  )

  _arguments -C \
    '(-h --help)'{-h,--help}'[Show help]' \
    '1:command:->subcmd' \
    '*::arg:->args'

  case $state in
    subcmd) _describe 'command' subcmds ;;
    args)
      case $words[1] in
        deploy) _arguments '--env[Target environment]:env:(dev staging prod)' ;;
      esac
      ;;
  esac
}

Place in a file named _mycli on your fpath, then ensure the directory is registered:

# In .zshrc, BEFORE compinit:
fpath=(~/.zsh/completions $fpath)
autoload -Uz compinit && compinit

Or source inline with compdef _mycli mycli (no fpath needed). The reference files have deeper coverage: glob-qualified completions, _files, _hosts, _values, and async completion patterns.


Verification Checklist

Before returning any shell script, check:

  • Shebang matches the target shell. #!/usr/bin/env bash for bash, #!/usr/bin/env zsh for zsh, #!/bin/sh for POSIX sh. Never #!/bin/bash (not portable across distros).
  • set -euo pipefail present for bash and zsh scripts. For POSIX sh: set -eu (no pipefail).
  • Variables are quoted. "$var" not $var, unless word splitting is intentional.
  • No shell-isms in the wrong shell. No [[ ]] in #!/bin/sh. No BASH_SOURCE in zsh. No bash arrays in POSIX sh.
  • Glob safety. POSIX sh: guard with [ -e "$f" ] || continue. Zsh: use (N) qualifier. Bash: shopt -s nullglob or guard.
  • Array indexing matches the shell. Bash: 0-indexed. Zsh: 1-indexed. POSIX sh: no arrays.
  • printf over echo for anything non-trivial (echo behavior varies across shells and platforms).

Reference Files

  • references/zsh.md - Zsh 5.9/5.10 patterns, glob qualifiers, arrays, parameter expansion, completions, autoloading, dotfile config, prompt hooks, zsh-only features, 5.10 additions (non-forking ${ }, namerefs, SRANDOM), bash porting matrix
  • references/bash.md - Bash 5.3 patterns, parameter expansion, arrays, conditionals, process substitution, error handling, traps, heredocs, coprocesses, bash 5.x features (non-forking ${ cmd; }, GLOBSORT, SRANDOM), script template
  • references/posix-sh.md - Portable POSIX sh patterns, what's POSIX and what's not, bashism avoidance checklist, which-sh-am-I, arithmetic, parameter expansion, portable conditionals
  • references/alt-shells.md - Fish 4.6 (syntax, functions, completions, config, 4.6 additions), tcsh/csh 6.24 (syntax, when you'll encounter it), nushell 0.111 (structured pipelines, types), elvish 0.22/oils 0.37 (brief)
  • references/ssh-tmux-autostart.md - safe shell startup pattern for interactive SSH sessions that attach to tmux without breaking non-interactive commands

Output Contract

See skills/_shared/output-contract.md for the full contract.

  • Skill name: COMMAND-PROMPT
  • Deliverable bucket: audits
  • Mode: conditional. When invoked to analyze, review, audit, or improve existing repo content, emit the full contract -- boxed inline header, body summary inline plus per-finding detail in the deliverable file, boxed conclusion, conclusion table -- and write the deliverable to docs/local/audits/command-prompt/<YYYY-MM-DD>-<slug>.md. When invoked to answer a question, teach a concept, build a new artifact, or generate content, respond freely without the contract.
  • Severity scale: P0 | P1 | P2 | P3 | info (see shared contract; only used in audit/review mode).

Related Skills

  • firewall-appliance - OPNsense/pfSense uses tcsh/csh on FreeBSD. That skill handles the BSD firewall context; this skill covers tcsh syntax in general.
  • ansible - Ansible shell/command modules have their own idiosyncrasies beyond raw shell scripting. Use ansible for playbook work.
  • ci-cd - CI shell blocks run in restricted environments (no interactive features, possibly no bash). Use ci-cd for pipeline design; use this skill for the shell syntax within them.

Rules

  1. Detect the shell first. Check shebang, file extension, or ask. Don't assume bash when the user might mean zsh.
  2. Load the right reference. Don't wing zsh arrays or bash parameter expansion from memory - the subtle differences justify loading the reference every time.
  3. Shebang is #!/usr/bin/env <shell>. Not #!/bin/bash. The env form is portable across distros. Exception: #!/bin/sh for POSIX scripts (this IS the standard form).
  4. set -euo pipefail in every bash/zsh script. No exceptions for scripts beyond a one-liner.
  5. User's interactive shell is zsh. When writing commands for the user to run locally, use zsh syntax. Bash for scripts and remote machines unless the script specifically needs zsh.
  6. Don't mix shell syntaxes. A bash script uses bash idioms. A zsh script uses zsh idioms. "Works in both" compromises use neither well and confuse readers.
  7. Quote your variables. "$var" is the default. Unquoted $var is the exception that needs justification.
Related skills
Installs
14
GitHub Stars
6
First Seen
Apr 1, 2026