
writing-shell-scripts
by czottmann
My personal, current, in-production configuration system for Claude Code featuring custom agents, skills, and global behavior rules.
SKILL.md
name: writing-shell-scripts description: Use this before writing any shell scripts - establishes fish shell syntax, gum integration patterns, and script organization conventions
Writing Shell Scripts
You are writing shell scripts for personal developer tooling and system automation. All scripts use fish shell (not bash/POSIX) and gum for interactive components. The target environment is macOS with fish as the default shell.
Core Principles
- Fish-first — Never use bash syntax. Fish has different control flow, variable handling, and string manipulation.
- Gum for all interaction — Any user input, selection, confirmation, or styled output goes through gum. No fallbacks.
- Fail fast, fail loud — Check preconditions early. Exit with clear errors rather than silent failures.
- No over-engineering — These are personal scripts. Don't add configurability, help systems, or abstractions you don't need yet.
Fish Syntax Guide
Variables
# Fish
set myvar "value"
set -x EXPORTED_VAR "value" # export
set -e myvar # unset
# NOT fish (bash)
myvar="value"
export EXPORTED_VAR="value"
Conditionals
# Fish uses `test` or direct commands
if test -f "$file"
echo "exists"
else if test -d "$dir"
echo "is directory"
end
# Status checks
if command -q git
# git is available
end
# NOT fish
if [ -f "$file" ]; then ... fi
if [[ ... ]]; then ... fi
Loops
# Fish
for item in $list
echo $item
end
while read -l line
echo $line
end < file.txt
# NOT fish
for item in "${list[@]}"; do ... done
String Manipulation
Use string, not sed/awk:
string replace "old" "new" $text
string split ":" $PATH
string match -q "*.txt" $filename
string trim $input
Command Substitution
# Fish uses ( )
set result (command)
# NOT fish
result=$(command)
result=`command`
No Word Splitting
Fish doesn't split variables on whitespace. "$var" and $var behave the same for single values. Lists are explicit.
Gum Patterns
User Input
set name (gum input --placeholder "Project name")
set password (gum input --password --placeholder "Enter password")
Selection (Single)
set choice (gum choose "Option A" "Option B" "Option C")
set file (gum file /path/to/start) # file picker
Selection (Multiple)
set choices (gum choose --no-limit "one" "two" "three")
# $choices is now a fish list
Confirmation
if gum confirm "Delete all logs?"
rm -rf logs/
end
Styled Output
gum style --foreground 212 --bold "Success!"
gum style --border rounded --padding "1 2" "Boxed message"
Progress/Spinners
gum spin --title "Installing..." -- long_running_command
# or for pipeline progress:
some_command | gum pager
Formatted Output
gum format --type markdown < README.md
Combining Patterns
set action (gum choose "deploy" "rollback" "cancel")
if test "$action" = "cancel"
exit 0
end
if gum confirm "Proceed with $action?"
do_the_thing $action
end
Error Handling & Safety
Precondition Checks
# Check required commands exist
for cmd in gum jq curl
if not command -q $cmd
fatal "$cmd is required but not installed"
end
end
# Check required env vars
if not set -q API_TOKEN
fatal "API_TOKEN environment variable not set"
end
# Check file exists
if not test -f "$config_file"
fatal "Config file not found: $config_file"
end
Status Checks After Commands
if not curl -s "$url" -o "$output"
error "Failed to download from $url"
exit 1
end
Confirm Before Destructive Actions
if gum confirm "Delete all files in $target_dir?"
rm -rf "$target_dir"/*
success "Cleaned $target_dir"
else
info "Aborted"
end
Output Helpers
These functions are globally installed in ~/.config/fish/functions/:
success— green ✓, for completed actionsinfo— blue •, for neutral statuswarn— yellow ⚠, for non-fatal issueserror— orange ✗, for failures (stderr)fatal— red ✗, for failures that exit (stderr + exit 1)
Script Organization
Where Scripts Live
| Location | Purpose |
|---|---|
~/.config/fish/functions/ | Reusable functions (auto-loaded by fish) |
~/.local/bin/ | Standalone executable scripts |
<project>/bin/ | Project-specific scripts |
Naming Conventions
- Lowercase, hyphens for separators:
deploy-staging,cleanup-logs - Functions match their filename:
foo.fishcontainsfunction foo - No
.fishextension for standalone scripts in~/.local/bin/or<project>/bin/(use shebang)
Shebang
#!/usr/bin/env fish
Script Structure Template
#!/usr/bin/env fish
# --- Preconditions ---
if not command -q gum
fatal "gum is required"
end
# --- Configuration ---
set -l target_dir ~/Downloads
set -l max_age 30
# --- Main logic ---
# ... your code here ...
Multi-File Tools
For larger tools, keep a main entry point and source helpers:
~/.local/bin/mytool # main script
~/.local/bin/helpers/mytool-helpers.fish # supporting helpers (sourced)
Or keep it self-contained with local functions defined at the top of the script.
Style Guide
Formatting
Use fish_indent to format scripts:
fish_indent -w script.fish
Variable Naming
set -l local_var "value" # snake_case for locals
set -g global_var "value" # snake_case for globals
set -x EXPORTED_VAR "value" # UPPER_SNAKE for exports/env vars
Function Naming
function do_something # snake_case
function mytool_helper # prefix with tool name for helpers
Quoting
Always quote variables in arguments, even though fish doesn't word-split:
# Preferred
echo "$myvar"
test -f "$file"
# Avoid (works, but inconsistent)
echo $myvar
Line Length
Keep lines under 100 characters. Break long pipelines:
cat "$file" \
| string match -r 'pattern' \
| sort -u
Blank Lines
- One blank line between logical sections
- One blank line before
endin longer functions
Documentation
Every script must have --help:
#!/usr/bin/env fish
argparse 'help' 'force' 'verbose' -- $argv
or return
function usage
echo '
script-name
Brief description of what this script does.
USAGE:
script-name [OPTIONS] <required-arg>
ARGUMENTS:
required-arg What this argument is for
OPTIONS:
--force Override existing files
--verbose Show detailed output
--help Show this usage description
REQUIRES:
- gum
- any other dependencies
OUTPUTS:
- What the script produces
'
end
if set -q _flag_help
usage
exit 0
end
# ... rest of script
Help Text Sections (in order)
- Script name (header line)
- Brief description
USAGE:— command syntaxARGUMENTS:— positional args (if any)OPTIONS:— all flags with descriptionsREQUIRES:— dependencies (optional)OUTPUTS:— what it produces (optional)
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です


