Skip to content
Development
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

From plugin
opinionated-claude-skills
919 skills3 agents
Install
$ npx -y skills add Pyroxin/opinionated-claude-skills --skill fish-shell-scripting --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/fish-shell-scripting

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

fish-shell-scripting.SKILL.md
name: fish-shell-scripting
description: 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).

Fish Shell Scripting

**Related skills:**

  • `software-engineer` - General scripting design principles
  • `python-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/sh` compatibility
  • 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 `-g` or `-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: 1. config.fish runs on every session 2. Universal variable already contains previous value 3. Append adds duplicate entries 4. 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`, `readlink` behave 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-in

Read more
Ships withopinionated-claude-skills

This project descends from the personal prompts I'd been keeping for Claude Code prior to the release of skills and plugins. Over time it's also evolved into a sandbox where I figure out what makes Claude reliably good at a task, and find prompts that work.

Get the whole plugin

Other skills on opinionated-claude-skills.