Agent skill
fish-shell-scripting
Fish shell scripting judgment frameworks and critical idioms. Use when writing Fish scripts or shell automation. Focuses on when to use Fish vs bash, macOS/Fedora compatibility requirements, and Fish-specific patterns that prevent bugs (universal variable anti-patterns, wrapper functions, interactive guards).
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/fish-shell-scripting
SKILL.md
Fish Shell Scripting
Related skills:
software-engineer- General scripting design principlespython-programmer- When shell complexity exceeds ~100 lines, consider Python
<core_philosophy> Fish is a user-friendly shell that prioritizes correctness over POSIX compatibility. Its lists-not-strings semantics eliminates entire classes of bugs common in bash/zsh. The limitation is portability, not capability.
Key insight: Fish handles sophisticated automation well. Choose Fish when you control the environment; choose bash when you don't. </core_philosophy>
Platform Requirements
<platform_requirements>
Compatibility: All Fish scripts MUST work on both macOS and Fedora Linux. Handle platform differences via uname detection.
Quote paths with spaces: Always quote file paths even though Fish has no word splitting. Fish doesn't need quotes technically (no word splitting), but quoting anyway: (1) makes intent explicit for readability, (2) builds consistent habits when switching between shells, (3) ensures compatibility when passing to external commands that may interpret spaces.
Shebang: Use #!/usr/bin/env fish for portability across installation locations.
</platform_requirements>
When to Use Fish vs Other Tools
<fish_vs_alternatives> Use Fish for:
- Automation on systems where Fish is installed (dev environments, personal machines, containers you control)
- Scripts benefiting from lists-not-strings semantics and strong scoping
- Complex CLI tools requiring argument parsing (
argparse+fish_opt) - Docker/container orchestration, dotfiles, local tooling
Use bash for:
- POSIX compliance requirements or
/bin/shcompatibility - CI/CD pipelines in uncontrolled environments
- Distribution to users who may not have Fish
Use a programming language for:
- Heavy data structure manipulation or API processing
- When shell semantics aren't the primary concern
- Scripts exceeding ~100 lines with complex logic </fish_vs_alternatives>
Critical Differences from POSIX Shells
<posix_differences>
No word splitting: Variables don't split on spaces. set name "foo bar"; echo $name is one argument. This eliminates a major source of bugs in bash/zsh.
All variables are lists: A "string" is a one-element list. Indexing is 1-based: $PATH[1], $PATH[-1].
No VAR=value syntax: Use set command. set -gx VAR value (global exported), set -lx VAR value (local exported).
Command substitution splits on newlines only: set lines (cat file) creates one element per line. For space-splitting: string split " ".
</posix_differences>
Scoping
<scoping_decision> Choosing a scope:
| Need | Scope | Example Use Case |
|---|---|---|
| Temporary within function/block | -l |
Loop variables, intermediate results |
| Shared across session | -g |
Current project settings, temporary overrides |
| Available to child processes | -gx or -lx |
PATH, EDITOR, build flags |
| Persist across sessions (Fish UI only) | -U |
fish_color_*, key bindings, prompt config |
Flag combinations:
-l(local): Dies when block ends-g(global): Session-scoped, not inherited by child processes-x(exported): Available to child processes (combine with-gor-l)-U(universal): Persists across all sessions, survives reboots
Example: set -gx EDITOR vim (global + exported), set -lx DEBUG 1 (local + exported to children).
CRITICAL: Environment variables (PATH, EDITOR, etc.) should NEVER be universal. Universal scope is for Fish UI config only. </scoping_decision>
Universal Variable Anti-Pattern
<universal_variable_antipattern> NEVER use universal variables for PATH or environment variables. Universal scope is for Fish UI config only (colors, key bindings, prompt).
Why this matters: Universal variables persist to disk and are shared across all Fish sessions. If you append to PATH in config.fish using universal variables, it grows indefinitely on every shell start because:
- config.fish runs on every session
- Universal variable already contains previous value
- Append adds duplicate entries
- This compounds across reboots
# WRONG: Grows PATH indefinitely on every shell start
set -U fish_user_paths ~/bin $fish_user_paths
# RIGHT: Session-scoped (recalculated fresh each session)
set -gx PATH ~/bin $PATH
# RIGHT: Use fish_add_path once interactively (idempotent, uses universal internally)
fish_add_path ~/bin
Safe universal variable uses: fish_color_*, fish_key_bindings, fish_prompt, fish_greeting.
</universal_variable_antipattern>
Cross-Platform Patterns
<cross_platform>
OS detection: switch (uname); case Darwin; ...; case Linux; ...; end
Conditional PATH: test -d ~/.cargo/bin; and fish_add_path ~/.cargo/bin
Platform-specific utilities to watch for:
- GNU vs BSD commands:
sed,find,date,stat,readlinkbehave differently - Package managers: Homebrew (macOS) vs DNF (Fedora)
- Paths:
/usr/local/bin(macOS Homebrew) vs/usr/bin(Fedora) </cross_platform>
Critical Idioms
<critical_idioms>
Wrapper functions require --wraps and $argv:
function ls --wraps=ls --description "ls with color"
command ls --color=auto $argv
end
- Without
--wraps=ls: Completions break (Fish doesn't know what command to complete for) - Without
$argv: Arguments are swallowed (user's flags/paths ignored)
Argument parsing: Use argparse + fish_opt for complex CLI tools. Check flags with set -q _flag_name.
Guard interactive code: Non-interactive shells (SSH, rsync) execute config.fish. Guard output:
if status is-interactive
echo "Welcome!"
end
Without this guard: SSH file transfers, rsync, and scp can fail because unexpected output corrupts the protocol.
Use string builtin: Prefer string match, string split over external grep/cut. Benefits: faster execution (no fork), consistent cross-platform behavior, proper exit codes.
and/or for control flow: Chain commands with and/or, not just &&/||. Both work, but and/or are Fish's native idiom.
</critical_idioms>
Organization
<organization_decisions>
| Content Type | Location | Why |
|---|---|---|
| Functions (reusable commands) | ~/.config/fish/functions/name.fish |
Autoloaded on first use, not at startup—keeps shell fast |
| Topical config (per-tool setup) | ~/.config/fish/conf.d/tool.fish |
Auto-sourced alphabetically, keeps concerns separated |
| Interactive-only setup | config.fish with status is-interactive |
Guards against breaking non-interactive use |
| Environment variables | conf.d/ with set -gx |
Session-scoped, recalculated fresh each session |
Minimal config.fish: Keep it to ~15 lines of orchestration. Put actual config in conf.d/ files.
Autoload advantage: Functions in functions/ are loaded only when first called, not at startup. This keeps shell startup fast even with many custom functions.
</organization_decisions>
Common Mistakes
<common_mistakes>
From Bash Users
<from_bash>
- Using
VAR=valuesyntax: Fish usesset VAR value. TheVAR=value commandpattern doesn't exist. - Expecting word splitting:
set x "foo bar"; echo $xis ONE argument in Fish. This is a feature, not a bug. - Using
export VAR=value: Fish usesset -gx VAR value(global exported). - Using
$(...): Fish uses(...)for command substitution, not$(...). - Using
&&/||exclusively: Works in Fish, butand/orare the native idiom. - Using
[[...]]tests: Fish usestestor[...], not bash's[[...]]. </from_bash>
From Zsh Users
<from_zsh>
- Array indexing from 0: Fish arrays are 1-indexed like zsh, but syntax differs:
$array[1]not$array[1]or${array[1]}. - Expecting
setopt/unsetopt: Fish configuration works differently—useset -Ufor persistent settings. - Using zsh-specific globbing: Fish globbing is simpler; complex patterns may need different approaches. </from_zsh>
From Any Shell Background
<from_any_shell>
- Appending to universal variables in config.fish: Creates infinite PATH growth. Use
set -gxor one-timefish_add_path. - Not guarding interactive code: Breaks SSH/rsync file transfers with protocol errors.
- Forgetting
$argvin wrapper functions: Silently swallows all user arguments. - Using
(whoami)instead of$USER: Unnecessary subshell; environment variable is faster. - Using
(hostname)instead of$hostname: Same issue—use the variable. - Using
aliasinstead ofabbr: Abbreviations expand visibly in history, making commands reproducible. Aliases hide what actually ran. - Treating universal variables like environment variables: Universal is for Fish UI config only (colors, bindings, prompt). </from_any_shell> </common_mistakes>
Resources
Local Documentation:
Typical locations (check these paths directly):
- macOS (Homebrew Apple Silicon):
/opt/homebrew/share/doc/fish/ - macOS (Homebrew Intel):
/usr/local/share/doc/fish/ - Fedora/Linux:
/usr/share/doc/fish/
Man pages:
- macOS (Homebrew Apple Silicon):
/opt/homebrew/share/man/man1/fish*.1 - macOS (Homebrew Intel):
/usr/local/share/man/man1/fish*.1 - Fedora/Linux:
/usr/share/man/man1/fish*.1
To find dynamically (from Fish shell):
ls "$(dirname (realpath (command -s fish)))/../share/doc/fish"
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?