
shell-script-installer
Use this skill when the user wants to create, write, or install shell scripts. Prefers Fish shell, h
Shell Script Installer Skill
This skill helps you create and install shell scripts to the user's PATH.
When to Use This Skill
Activate this skill when the user:
- Asks to create a shell script (fish, bash, zsh, sh)
- Wants to install a script to their PATH
- Needs a command-line utility or tool
- Mentions writing executable scripts
Shell Preference
Default to Fish shell for all new scripts unless:
- User explicitly requests bash/zsh/sh
- Building on or extending an existing bash script
- The script requires bash-specific features not available in Fish
- There's a compelling compatibility reason (e.g., sharing with non-Fish users)
Installation Directory
Install all scripts to: ~/bin/scripts
IMPORTANT: ~/bin/scripts is a git repository. All script creation and editing must use proper git workflow.
Workflow
Follow these steps in order:
1. Understand Requirements
- Ask what the script should do
- Determine the script name (without extension)
- Determine shell interpreter (default: fish, fallback to bash only if necessary)
2. Set Up Git Workspace
- Navigate to the scripts repository:
cd ~/bin/scripts - Check current git status to ensure clean state
- Create a new branch for this script:
git checkout -b add-<script-name> - The branch name should be descriptive (e.g.,
add-backup-tool,add-git-helper)
3. Write the Script
- Create the script in the repository:
~/bin/scripts/<script-name> - Include proper shebang line:
- For Fish:
#!/usr/bin/env fish - For Bash:
#!/usr/bin/env bash
- For Fish:
- Add helpful comments
- Include error handling where appropriate
- Follow shell scripting best practices for the chosen shell
4. Review with User
- Show the complete script to the user
- Explain what it does
- Ask if they'd like to make any changes
5. Iterate (if needed)
- If user requests changes, edit the script in place
- Each iteration happens on the same git branch
- Repeat review until user is satisfied
6. Test (Interactive)
- Ask the user if they want to test the script before committing
- Make the script executable:
chmod +x ~/bin/scripts/<script-name> - Offer a test command or let them test manually
- Test under realistic stdin/TTY conditions — see "Testing Under Realistic stdin/TTY Conditions" below. An AI agent's Bash tool typically runs scripts with stdin redirected from
/dev/null(or another pipe), which silently hides whole classes of bugs. If the script will be invoked interactively by a human, it MUST be tested that way before declaring it working. - Review test results with the user
- If tests fail, go back to iteration (step 5)
7. Commit and Merge
- Ask for confirmation: "Ready to commit and install
<script-name>?" - If confirmed:
- Stage the script:
git add <script-name> - Commit with detailed structured message (see "Git Commit Message Standards" section below)
- Include Features section listing all capabilities
- Include Technical details section with implementation notes
- Include Co-Authored-By attribution for AI assistance
- Switch back to main branch:
git checkout main - Merge the feature branch:
git merge add-<script-name> - REQUIRED: Delete the feature branch:
git branch -d add-<script-name>- CRITICAL: Always delete merged feature branches to keep the repository clean
- Never leave merged branches lingering in the repository
- This is mandatory, not optional
- Stage the script:
8. Verify PATH
- Check if ~/bin/scripts is in the user's PATH
- If not, inform the user and provide instructions to add it:
# For Fish: add to ~/.config/fish/config.fish fish_add_path $HOME/bin/scripts# For bash: add to ~/.bashrc or ~/.bash_profile # For zsh: add to ~/.zshrc export PATH="$HOME/bin/scripts:$PATH"
9. Final Verification
- Test that the script is accessible:
which <script-name> - Provide usage instructions
- Suggest restarting the shell or sourcing the config if PATH was just updated
Structured Logging Standards
CRITICAL: All scripts must implement structured logging using the logger command.
Logging Format
- Use
loggercommand with structured key=value format - Tag format:
scriptname[PID](use$$for PID in bash,$fish_pidin fish) - IMPORTANT: In Fish, always use
$fish_pidto get the process ID, NOT$$or%self - Preserve user-facing output (echo/printf) while adding diagnostic logging to syslog
Log Level Mapping
Map script events to appropriate log levels:
-
user.info: Process status, file operations, success events- Example: "Processing file", "Operation completed successfully"
-
user.warning: Unsupported formats, non-critical issues, recoverable errors- Example: "Unsupported file format", "Feature not available"
-
user.error: Missing dependencies, failures, exit conditions- Example: "Required tool not found", "Failed to write file"
-
user.debug: File creation, skipped files, detailed operations- Example: "Created directory", "Skipping cached file"
Logging Examples
Fish:
logger -t (basename (status filename))"[$fish_pid]" -p user.info "action=start status=processing"
logger -t (basename (status filename))"[$fish_pid]" -p user.error "action=fail error=\"missing dependency\""
Bash:
logger -t "$(basename "$0")[$$]" -p user.info "action=start status=processing"
logger -t "$(basename "$0")[$$]" -p user.error "action=fail error=\"missing dependency\""
Input/Output Standards (REQUIRED FOR DATA-PROCESSING SCRIPTS)
CRITICAL: Scripts that process data (read input, transform, write output) MUST follow these I/O standards.
Input Handling
Default to stdin when no file argument is provided:
set input_file "-"
if test (count $argv) -gt 0
set input_file $argv[1]
end
# Validate file exists (if not stdin)
if test "$input_file" != "-" -a ! -f "$input_file"
echo "Error: Input file not found: $input_file" >&2
log_error "action=read_input status=not_found file=\"$input_file\""
exit 2
end
Output Handling
Default to stdout, but provide -o/--output flag for file output:
set output_file "-"
set append_mode 0
argparse 'o/output=' 'a/append' -- $argv
if set -q _flag_output
set output_file $_flag_output
end
if set -q _flag_append
set append_mode 1
end
# Validate: append requires output file
if test $append_mode -eq 1 -a "$output_file" = "-"
echo "Error: --append requires --output to specify a file" >&2
exit 2
end
Stream Separation (CRITICAL)
stdout = data, stderr = messages
# WRONG - mixes data and messages
echo "Processing 100 items..."
echo "$result_data"
# CORRECT - separates streams
echo "Processing 100 items..." >&2 # Progress to stderr
echo "$result_data" # Data to stdout
Why: Pipes capture stdout only. Messages on stderr appear to user but don't pollute the data stream.
Structured Data Output
For structured data, prefer TSV (tab-separated values):
# TSV format (no header, easy to pipe)
echo -e "$url\t$title\t$format\t$notes"
Benefits:
- Easy to process with
cut,awk,sort - No quoting issues like CSV
- Append-friendly (no header row to skip)
- Pipe-friendly for tool chaining
Progress Output Pattern (REQUIRED FOR MULTI-ITEM PROCESSING)
For scripts that iterate over multiple items or perform long-running operations:
# Auto-detect interactive vs batch mode
set progress_mode 0
if isatty stderr
set progress_mode 1 # Interactive - show progress by default
end
# Parse flags (in argparse)
argparse 'progress' 'no-progress' -- $argv
# Explicit flags override auto-detection
if set -q _flag_progress
set progress_mode 1
end
if set -q _flag_no_progress
set progress_mode 0
end
# In processing loop:
set current 0
set total (count $items)
for item in $items
set current (math $current + 1)
if test $progress_mode -eq 1
# Non-scrolling in-place update (\r returns to start of line)
printf "\r[%d/%d] %s" $current $total "$item" >&2
end
# ... do work ...
end
# Final newline to complete the progress line
if test $progress_mode -eq 1
printf "\n" >&2
end
# Summary stats to stderr (always show, even in no-progress mode)
echo "Results: $valid valid, $invalid invalid" >&2
Key Points:
- Use
printf "\r..."for non-scrolling in-place updates - Progress goes to stderr, not stdout
- Auto-detect TTY, but allow explicit override
- Always show final summary statistics
Pipe Support Standards (REQUIRED FOR DATA-PROCESSING SCRIPTS)
Design Principles
- Composable: Output should be valid input for similar tools
- Silent on success: Only output data, not progress (unless --progress)
- No prompts: Never ask for interactive input in pipe mode
- Handle broken pipes: Gracefully handle downstream commands exiting early
Making Scripts Pipe-Friendly
# Example: Enable piping between related tools
cat urls.txt | extract-urls | validate-urls | download-videos
# Each script in the chain:
# - Reads from stdin OR file
# - Writes data to stdout
# - Writes progress/errors to stderr
# - Uses consistent format (one-per-line or TSV)
Example Usage Patterns
Add these to your usage() function:
echo "Examples:"
echo " $SCRIPT_NAME input.txt -o output.txt"
echo " cat input.txt | $SCRIPT_NAME"
echo " $SCRIPT_NAME < input.txt > output.txt"
echo " $SCRIPT_NAME input.txt | other-tool"
Best Practices
CLI Standards (REQUIRED)
Core Flags (All Scripts)
- Always include
--help/-hargument with usage information - Always include
--version/-vargument showing script version - Always include
--testargument that runs unit and regression tests of the code - Always include
--fish-completionsargument that installs fish shell tab completions to~/.config/fish/completions/<script-name>.fish- Completions must include all flags (--help, --version, --test, --fish-completions, and any script-specific flags)
- Completions should include context-aware argument completion where applicable
- Must error if completion file already exists (prevents accidental overwrite)
Data-Processing Script Flags (REQUIRED for scripts that process input→output)
Scripts that process input data and produce output MUST also include:
-o/--output FILE- Write output to FILE instead of stdout (default: stdout)-a/--append- Append to output file instead of overwriting (requires -o)
Multi-Item Processing Flags (REQUIRED for scripts that iterate over items)
Scripts that process multiple items or perform long-running operations MUST include:
--progress- Force in-place progress updates (even in batch mode)--no-progress- Suppress progress updates (even in interactive mode)- Default behavior: Auto-detect based on
isatty stderr(interactive = progress on, batch = progress off)
General Guidelines
- Document all command-line options clearly
- Follow standard Unix conventions for flags and arguments
Installation Documentation (REQUIRED)
- Include installation instructions in a comment at the top of every script
- Scripts should be self-documenting about where they belong in the filesystem
- Examples:
- Standalone command-line tool:
# Installation: Copy to ~/bin/scripts - Fish function that needs to be sourced:
# Installation: Copy to ~/.config/fish/functions - Shell configuration addon:
# Installation: Source from ~/.config/fish/config.fish or ~/.bashrc
- Standalone command-line tool:
- Make it clear if there are any post-installation setup steps required
For Fish Scripts (Preferred)
CRITICAL: Fish shell has specific behaviors that differ from bash/zsh. See the comprehensive guide at ~/fish-shell-rules.md for detailed rules, examples, and bug history from this project.
Most Critical Fish-Specific Rules
-
Variable Scoping: Use
set -g(notset -l) for variables accessed across functions# WRONG: set -l urls "..." # Will be empty in called functions # CORRECT: set -g urls "..." # Visible everywhere -
Directory Changes: Fish
(cd dir && cmd)does NOT create subshell - always save/restore$PWD# WRONG: (cd "$temp" && process) # Changes parent directory! # CORRECT: set orig $PWD; cd "$temp"; process; cd "$orig" -
Multi-line Output: Use
| string collectto preserve newlines in command substitution# WRONG: set output (command) # Collapses newlines to spaces # CORRECT: set output (command | string collect) -
stdin Detection: Always check for empty args before reading stdin
# WRONG: if not isatty stdin # False positive with redirects # CORRECT: if test (count $argv) -eq 0; and not isatty stdin -
Array Iteration: Use direct iteration (never echo/split)
# WRONG: for x in (echo "$array" | string split \n) # CORRECT: for x in $array -
String Operations: Use Fish's
stringbuilt-in instead of grep/sed/awk# WRONG: echo "$text" | grep "pattern" # CORRECT: string match "*pattern*" $text
See ~/fish-shell-rules.md for complete rules and the commit history showing why these rules exist.
General Fish Best Practices
- Use
#!/usr/bin/env fishfor portability - Include usage/help information with
--helpand--versionflags - Use Fish's built-in error handling and status checks
- Leverage Fish's modern syntax and features (argparse, functions)
- Make scripts user-friendly with clear error messages
- Avoid hardcoded paths when possible
- Implement structured logging as described above
For Bash Scripts (When Necessary)
- Use
#!/usr/bin/env bashfor portability - Include usage/help information with
--helpand--versionflags - Add error handling with
set -eor appropriate error checks - Follow bash best practices
- Implement structured logging as described above
Path Handling
- Be careful with
cdcommands: When scripts change directories, subsequent operations with relative paths may break - Use absolute paths when possible, especially after directory changes
- Store original working directory if you need to return:
set origin_dir (pwd)(fish) ororigin_dir=$(pwd)(bash) - Test path resolution thoroughly, especially in refactored code
Tool Design Philosophy
- Make tools general-purpose rather than solution-specific when possible
- Design for reusability and composability
- Separate concerns: one tool should do one thing well
- Consider how the tool might be used in different contexts
Configuration Management
- Separate configuration from code whenever practical
- Extract prompts, templates, and settings to separate files
- Use environment variables or config files for user-customizable values
- Document configuration options clearly
Testing Requirements
- Test thoroughly after major refactoring, especially:
- Path resolution and file access
- Directory changes and relative paths
- Error handling and edge cases
- All command-line flags and options
- Verify structured logging outputs correct levels and formats
- Test with both valid and invalid inputs
- Test under realistic stdin/TTY conditions — see next section.
Testing Under Realistic stdin/TTY Conditions
The single biggest source of "works in CI / hangs in real life" bugs.
When an AI agent runs a script via the Bash tool, the script's stdin is usually /dev/null (or another pipe), and stdout/stderr are pipes — not a terminal. A script that the user will run interactively at the terminal sees something completely different: stdin/stdout/stderr are all attached to a TTY. Several common failure modes only surface on the real TTY:
(cat | string collect)inside a fish function called from a pipeline reads the controlling TTY, not the function's piped stdin → hangs forever waiting for Ctrl-D.read -P "..." answerblocks indefinitely if the script ends up wired to a non-interactive stdin.isatty stdinreturns different things, which can flip code paths (auto-detect of progress, paging, prompts).- Editor-spawning logic (e.g.
$EDITOR $tmpfile) needs a real TTY to function; agent test runs may either hang on a vim that can't draw, or quietly skip the step. - Output formatting that depends on
tput colsorisatty stdoutlooks right in agent runs and broken when piped (or vice versa).
How to test correctly. Match the invocation context to the script's intended use:
| Intended use | Correct test invocation |
|---|---|
| Human runs in terminal | User runs it themselves in their real shell, OR allocate a PTY (see below) |
Piped into / out of (a | script | b) | Test with the actual pipe in place, both ends |
| Cron / launchd / CI | Test with stdin from /dev/null, no TTY |
| Mixed (interactive + pipe-friendly) | Test BOTH paths — they exercise different code |
Allocating a PTY from an agent context. If you (the AI) need to verify TTY-bound behavior yourself without the user, use script(1) (BSD/macOS variant) or expect/unbuffer to allocate a pseudo-terminal:
# macOS/BSD: -q quiet, -t 0 no timing file, runs cmd under a PTY
script -q /dev/null my-script --some-flag </dev/null
# Or, if `expect` is installed:
unbuffer my-script --some-flag
What to actually verify in a TTY-mode test:
- Script doesn't hang. Set an explicit timeout in the test wrapper (e.g.
timeout 30 script -q /dev/null ./my-script) so a hang fails the test instead of stalling forever. - If the script reads stdin from a pipeline upstream of itself, confirm that stdin actually reaches the consumer —
lsof -p <pid>on a hung process to inspect fd 0 is the definitive diagnostic. - Multi-line output that the user will see (commit messages, generated files, prompts) round-trips with newlines intact. Verify with
od -corcat -e, not by eye — fish list-join collapses\nto space and it's easy to miss in normal output. - If the script spawns
$EDITOR, test withEDITOR=true(succeeds, file unchanged) and a fake editor that mutates the file, to ensure both paths work.
The mindset: before declaring a script done, ask "what is stdin/stdout/stderr in the real invocation, and have I actually tested that configuration?" If the only test was cmd > /dev/null 2>&1 < /dev/null from inside an agent, the answer is no.
Example Script Templates
Fish Script Template (Preferred)
#!/usr/bin/env fish
# Script: example-script
# Version: 1.0.0
# Description: What this script does
# Installation: Copy to ~/bin/scripts
set VERSION "1.0.0"
set SCRIPT_NAME (basename (status filename))
function log_info
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.info $argv
end
function log_error
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.error $argv
end
function log_debug
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.debug $argv
end
function show_version
echo "$SCRIPT_NAME version $VERSION"
exit 0
end
function run_tests
log_info "action=test status=starting"
# Add unit and regression tests here
echo "Running unit tests..."
# Example test placeholder
# Replace with actual test logic
echo "All tests passed!"
log_info "action=test status=complete"
exit 0
end
function install_fish_completions
set -l completions_file ~/.config/fish/completions/$SCRIPT_NAME.fish
# Check if file already exists
if test -f "$completions_file"
echo "Error: Completions file already exists: $completions_file" >&2
echo "Remove it first if you want to regenerate completions." >&2
exit 1
end
# Ensure directory exists
mkdir -p ~/.config/fish/completions
# Generate and write completions
echo "# Fish completions for $SCRIPT_NAME
# Generated by $SCRIPT_NAME --fish-completions
# Complete flags
complete -c $SCRIPT_NAME -s h -l help -d 'Show help message'
complete -c $SCRIPT_NAME -s v -l version -d 'Show version information'
complete -c $SCRIPT_NAME -l test -d 'Run unit and regression tests'
complete -c $SCRIPT_NAME -l fish-completions -d 'Install fish shell completions'
# Add script-specific completions here
" > "$completions_file"
and begin
echo "Fish completions installed to: $completions_file"
echo ""
echo "Completions will be available in new fish shell sessions."
echo "To use them immediately in this session, run:"
echo " source $completions_file"
end
or begin
echo "Error: Failed to write completions file" >&2
exit 1
end
exit 0
end
function usage
echo "Usage: $SCRIPT_NAME [options]"
echo ""
echo "Description: What this script does"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -v, --version Show version information"
echo " --test Run unit and regression tests"
echo " --fish-completions Install fish shell completions"
exit 0
end
function main
log_info "action=start status=processing"
# Your script logic here
echo "Hello from example Fish script!"
log_info "action=complete status=success"
end
# Parse arguments
argparse 'h/help' 'v/version' 'test' 'fish-completions' -- $argv
or begin
usage
end
if set -q _flag_help
usage
end
if set -q _flag_version
show_version
end
if set -q _flag_test
run_tests
end
if set -q _flag_fish_completions
install_fish_completions
end
log_debug "action=init args=\"$argv\""
main
Bash Script Template (Fallback)
#!/usr/bin/env bash
set -e
# Script: example-script
# Version: 1.0.0
# Description: What this script does
# Installation: Copy to ~/bin/scripts
VERSION="1.0.0"
SCRIPT_NAME="$(basename "$0")"
log_info() {
logger -t "${SCRIPT_NAME}[$$]" -p user.info "$@"
}
log_error() {
logger -t "${SCRIPT_NAME}[$$]" -p user.error "$@"
}
log_debug() {
logger -t "${SCRIPT_NAME}[$$]" -p user.debug "$@"
}
show_version() {
echo "$SCRIPT_NAME version $VERSION"
exit 0
}
run_tests() {
log_info "action=test status=starting"
# Add unit and regression tests here
echo "Running unit tests..."
# Example test placeholder
# Replace with actual test logic
echo "All tests passed!"
log_info "action=test status=complete"
exit 0
}
install_fish_completions() {
local completions_file=~/.config/fish/completions/$SCRIPT_NAME.fish
# Check if file already exists
if [[ -f "$completions_file" ]]; then
echo "Error: Completions file already exists: $completions_file" >&2
echo "Remove it first if you want to regenerate completions." >&2
exit 1
fi
# Ensure directory exists
mkdir -p ~/.config/fish/completions
# Generate and write completions
cat > "$completions_file" << 'EOF'
# Fish completions for $SCRIPT_NAME
# Generated by $SCRIPT_NAME --fish-completions
# Complete flags
complete -c $SCRIPT_NAME -s h -l help -d 'Show help message'
complete -c $SCRIPT_NAME -s v -l version -d 'Show version information'
complete -c $SCRIPT_NAME -l test -d 'Run unit and regression tests'
complete -c $SCRIPT_NAME -l fish-completions -d 'Install fish shell completions'
# Add script-specific completions here
EOF
if [[ $? -eq 0 ]]; then
echo "Fish completions installed to: $completions_file"
echo ""
echo "Completions will be available in new fish shell sessions."
echo "To use them immediately in this session, run:"
echo " source $completions_file"
else
echo "Error: Failed to write completions file" >&2
exit 1
fi
exit 0
}
usage() {
echo "Usage: $SCRIPT_NAME [options]"
echo ""
echo "Description: What this script does"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -v, --version Show version information"
echo " --test Run unit and regression tests"
echo " --fish-completions Install fish shell completions"
exit 0
}
main() {
log_info "action=start status=processing"
# Your script logic here
echo "Hello from example bash script!"
log_info "action=complete status=success"
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
-v|--version)
show_version
;;
--test)
run_tests
;;
--fish-completions)
install_fish_completions
;;
*)
echo "Unknown option: $1"
usage
;;
esac
shift
done
log_debug "action=init args=\"$*\""
main
Enhanced Fish Template for Data-Processing Scripts
For scripts that process input and produce output (especially multi-item processing), use this enhanced template:
#!/usr/bin/env fish
# Script: process-items
# Version: 1.0.0
# Description: Process items from input and output results
# Installation: Copy to ~/bin/scripts
set VERSION "1.0.0"
set SCRIPT_NAME (basename (status filename))
# Logging functions
function log_info
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.info $argv
end
function log_error
logger -t "$SCRIPT_NAME[$fish_pid]" -p user.error $argv
end
function show_version
echo "$SCRIPT_NAME version $VERSION"
exit 0
end
function usage
echo "Usage: $SCRIPT_NAME [options] [input-file]"
echo ""
echo "Process items from input and output results."
echo ""
echo "Arguments:"
echo " input-file File containing items (default: stdin)"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -v, --version Show version information"
echo " -o, --output FILE Output file (default: stdout)"
echo " -a, --append Append to existing file instead of overwriting"
echo " --progress Force progress updates (even in batch mode)"
echo " --no-progress Suppress progress updates (even in interactive mode)"
echo " --test Run unit and regression tests"
echo " --fish-completions Install fish shell completions"
echo ""
echo "Examples:"
echo " $SCRIPT_NAME input.txt -o output.txt"
echo " cat input.txt | $SCRIPT_NAME"
echo " $SCRIPT_NAME input.txt | other-tool"
exit 0
end
function main
# I/O configuration
set input_file "-"
set output_file "-"
set append_mode 0
# Progress configuration (auto-detect TTY)
set progress_mode 0
if isatty stderr
set progress_mode 1
end
# Parse arguments
argparse 'h/help' 'v/version' 'o/output=' 'a/append' 'progress' 'no-progress' 'test' 'fish-completions' -- $argv
or begin
usage
exit 2
end
# Handle flags
if set -q _flag_help; usage; end
if set -q _flag_version; show_version; end
if set -q _flag_output; set output_file $_flag_output; end
if set -q _flag_append; set append_mode 1; end
if set -q _flag_progress; set progress_mode 1; end
if set -q _flag_no_progress; set progress_mode 0; end
# Get input file from remaining arguments
if test (count $argv) -gt 0
set input_file $argv[1]
if test "$input_file" != "-" -a ! -f "$input_file"
echo "Error: Input file not found: $input_file" >&2
log_error "action=read_input status=not_found file=\"$input_file\""
exit 2
end
end
# Validate append requires output file
if test $append_mode -eq 1 -a "$output_file" = "-"
echo "Error: --append requires --output to specify a file" >&2
exit 2
end
log_info "action=start input=\"$input_file\" output=\"$output_file\""
# Read input items
if test "$input_file" = "-"
set items (cat)
else
set items (cat $input_file)
end
# Process items with progress
set current 0
set total (count $items)
set results
for item in $items
set current (math $current + 1)
# Show progress (non-scrolling, in-place update)
if test $progress_mode -eq 1
printf "\r[%d/%d] Processing: %s" $current $total "$item" >&2
end
# Process item (replace with actual logic)
set result (process_item $item)
set -a results $result
end
# Complete progress line
if test $progress_mode -eq 1
printf "\n" >&2
end
# Summary to stderr (always show)
echo "Processed $total items" >&2
# Write output (data to stdout or file)
if test "$output_file" = "-"
printf "%s\n" $results
else
if test $append_mode -eq 1
printf "%s\n" $results >> "$output_file"
else
printf "%s\n" $results > "$output_file"
end
end
log_info "action=complete status=success count=$total"
end
function process_item
# Replace with actual processing logic
echo "processed: $argv[1]"
end
# Run main
main $argv
Common Errors to Avoid
NOTE: For Fish-specific errors (variable scoping, directory changes, stdin detection, etc.), see ~/fish-shell-rules.md which documents recurring bugs from this project's history.
1. Mixing Data and Messages on stdout
WRONG:
echo "Processing file..." # Goes to stdout
echo "$result" # Also goes to stdout
CORRECT:
echo "Processing file..." >&2 # Messages to stderr
echo "$result" # Data to stdout
2. Not Supporting stdin/stdout
WRONG: Only accepting file arguments
cat $argv[1] # Fails if no file specified
CORRECT: Default to stdin/stdout
set input_file "-"
if test (count $argv) -gt 0
set input_file $argv[1]
end
3. Using Relative Paths After cd
WRONG:
cd /some/directory
cat config.txt # Where is this file now?
CORRECT:
# Store original directory or use absolute paths
set origin_dir (pwd)
cd /some/directory
# ... work ...
cd $origin_dir
cat config.txt
4. Not Checking Exit Codes
WRONG:
curl $url > data.json
process_file data.json # What if curl failed?
CORRECT:
curl $url > data.json
if test $status -ne 0
log_error "action=download status=failed url=\"$url\""
exit 1
end
5. Silent Failures
WRONG:
if not command -v tool
exit 1 # User has no idea what happened
end
CORRECT:
if not command -v tool
echo "Error: 'tool' is not installed" >&2
echo "Install with: brew install tool" >&2
log_error "action=dependency_check status=missing tool=tool"
exit 3
end
6. Not Validating Flag Combinations
WRONG: Allowing --append without --output
# Should validate that append mode requires a file
CORRECT:
if test $append_mode -eq 1 -a "$output_file" = "-"
echo "Error: --append requires --output to specify a file" >&2
exit 2
end
7. Interactive Prompts in Pipeable Scripts
WRONG: Asking for user input in a data-processing script
read -P "Continue? (y/n): " answer
CORRECT: Use flags for all options, avoid prompts in pipe-friendly scripts
Troubleshooting
- Script not found after install: Check PATH and restart shell or source config
- Permission denied: Verify
chmod +xwas applied to the script - Command not found: Ensure
~/bin/scriptsis in PATH - Git merge conflicts: If conflicts occur, resolve manually or abort with
git merge --abort - Dirty git state: If repository has uncommitted changes, stash them first with
git stash - Branch already exists: Delete the old branch with
git branch -D add-<script-name>or use a different name
Git Workflow Notes
- Always work on a feature branch (never directly on main)
- Each new script gets its own branch:
add-<script-name> - MANDATORY: Always delete feature branches after merging - this is required, not optional
- Clean branches keep the repository organized and prevent confusion about what's in progress
- If you need to abandon a script, delete the branch without merging:
git checkout main && git branch -D add-<script-name>
Git Commit Message Standards
IMPORTANT: Use detailed, structured commit messages with the following format:
Add <script-name>: <brief one-line description>
Features:
- Feature 1 with details
- Feature 2 with details
- Feature 3 with details
Technical details:
- Implementation detail 1
- Implementation detail 2
- Configuration or dependency notes
Co-Authored-By: Claude <noreply@anthropic.com>
Guidelines:
- Subject line: Start with "Add <script-name>:" followed by brief description
- Features section: List all user-facing features and capabilities
- Technical details section: Include implementation notes, technologies used, dependencies
- Co-authorship: Always include AI assistance attribution
- Use bullet points for clarity
- Be specific about what was implemented
- Include any important configuration or setup notes
Example:
Add backup-tool: Automated backup script with compression and rotation
Features:
- Automatic backup of specified directories
- Gzip compression with configurable level
- Rotation policy (keeps last N backups)
- Email notifications on completion or failure
- Dry-run mode for testing
Technical details:
- Implemented in Fish shell with structured logging
- Uses rsync for efficient file copying
- Logger integration for syslog monitoring
- Configuration via environment variables
- Requires: rsync, gzip, mail utilities
Co-Authored-By: Claude <noreply@anthropic.com>