Claude-skill-registry fish-shell-config

Expert Fish shell configuration including config files, functions, abbreviations, prompts, environment variables, and scripting. Use when setting up Fish, creating aliases/functions, writing Fish scripts, configuring prompts, or migrating from bash. Keywords: fish shell, fish config, alias, abbr, function, prompt, fish script, config.fish, autoload

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fish-shell-config" ~/.claude/skills/majiayu000-claude-skill-registry-fish-shell-config && rm -rf "$T"
manifest: skills/data/fish-shell-config/SKILL.md
source content

Fish Shell Configuration Expert

Expert guidance for Fish shell setup, configuration, scripting, and interactive features based on official Fish documentation.

Core Principles

Fish philosophy differs fundamentally from bash:

  • Variables are lists - all Fish variables contain string lists, not scalars
  • No word splitting -
    "$var"
    and
    $var
    behave identically (already quoted)
  • Autoloading - functions load on-demand from
    ~/.config/fish/functions/
  • Universal variables - persist across sessions via
    -U
    flag
  • Explicit syntax -
    set VAR value
    instead of
    VAR=value

Configuration File Structure

Fish reads configs in this order:

~/.config/fish/
├── config.fish          # Main config (keep minimal)
├── conf.d/              # Auto-sourced configs (lexical order)
│   ├── 10-nix.fish     # Early: PATH and environment
│   ├── 20-direnv.fish  # Middle: tool hooks
│   ├── 30-starship.fish # Late: prompt setup
│   └── 40-aliases.fish  # Last: shortcuts and abbrs
└── functions/           # Autoloaded function definitions
    ├── nix-try.fish
    └── my-function.fish

Best practice: Keep

config.fish
minimal; use
conf.d/*.fish
for modular configs.

Functions vs Aliases vs Abbreviations

Functions (Recommended)

Where:

~/.config/fish/functions/name.fish
for autoloading

# ~/.config/fish/functions/ll.fish
function ll --description 'list all files'
    eza -la $argv
end

Benefits: Autoloaded on demand, persistent, scriptable, full control

Aliases (Creates Functions)

Where:

conf.d/40-aliases.fish

# Creates a function under the hood
alias g='git'
alias k='kubectl'

# Better: guard with type check
function __maybe_alias --argument-names name target
    if type -q $target
        alias $name="$target"
    end
end

__maybe_alias g git
__maybe_alias ll eza

Note: In Fish,

alias
creates a function, so it's callable from scripts.

Abbreviations (Interactive Only)

Where:

conf.d/40-aliases.fish

# Expands on SPACE/ENTER in interactive shells
abbr -a gco 'git checkout'
abbr -a gst 'git status -sb'
abbr -a glg 'git log --oneline --graph --decorate --all'
abbr -a kctx 'kubectl config use-context'

# Conditional abbreviation
if type -q kubectl
    abbr -a k 'kubectl'
end

Benefits: See expansion before execution, learn command syntax, purely interactive

Variable Scoping

Scope Flags

  • -l
    local: Current block/function only
  • -g
    global: Current session
  • -U
    universal: Persists across all sessions (stored in
    ~/.config/fish/fish_variables
    )
  • -x
    export: Visible to child processes (environment variable)

Common Patterns

# Session-only global export
set -gx EDITOR nvim

# Universal (survives restarts)
set -U fish_greeting ""  # disable greeting

# Local function variable
function my_func
    set -l temp_var "local value"
    # temp_var dies when function exits
end

# PATH manipulation (Fish handles it as a list)
set -gx PATH $HOME/.local/bin $PATH

# Check if set before setting
set -q EDITOR; or set -gx EDITOR nvim

Special Variables

  • $argv
    - function/script arguments (replaces bash
    $@
    ,
    $1
    ,
    $2
    ...)
  • $status
    - last command exit code (replaces bash
    $?
    )
  • $fish_pid
    - current shell PID (replaces bash
    $$
    )
  • $PATH
    - automatically colon-delimited when exported

Writing Functions

Basic Structure

function name --description 'what it does'
    # $argv contains all arguments
    command $argv
end

Function Options

  • --description 'text'
    - shown in completions
  • --wraps command
    - inherit completions from command
  • --argument-names var1 var2
    - name positional args

Advanced Example

function nix-try --description 'Try a package temporarily' \
                 --argument-names package
    if test (count $argv) -eq 0
        echo "Usage: nix-try <package>"
        return 1
    end

    # Command substitution with $()
    set -l store_path (nix build nixpkgs#$package --print-out-paths --no-link)

    if test $status -ne 0
        echo "Failed to build $package"
        return 1
    end

    $store_path/bin/$package
end

Editing Functions Interactively

funced function_name    # Edit in $EDITOR
funcsave function_name  # Save to ~/.config/fish/functions/

Control Flow

Conditionals

# Using 'test' command
if test -e /path/to/file
    echo "exists"
else if test $count -gt 5
    echo "greater than 5"
else
    echo "other"
end

# Using command exit status
if command_that_might_fail
    echo "succeeded"
end

# Using 'and'/'or'
command1; and command2  # command2 only if command1 succeeds
command1; or command2   # command2 only if command1 fails

Loops

# For loop over list
for file in *.txt
    echo "Processing $file"
end

# While loop
set -l count 0
while test $count -lt 10
    echo $count
    set count (math $count + 1)
end

# Iterate over command output (splits on newlines)
for line in (cat file.txt)
    echo "Line: $line"
end

Switch Statement

switch $argv[1]
    case start
        echo "Starting..."
    case stop
        echo "Stopping..."
    case '*'
        echo "Unknown command"
end

String Handling

Command Substitution

# Two equivalent forms
set output $(command)
set output (command)

# Splits on newlines only (not spaces)
set lines (cat file.txt)  # Each line = one list element

# Split on custom delimiter
set fields (echo "a:b:c" | string split ':')  # ['a', 'b', 'c']

String Operations (using
string
builtin)

# Split
echo "foo:bar:baz" | string split ':'

# Join
string join ',' a b c  # "a,b,c"

# Replace
string replace 'old' 'new' $var

# Match/regex
string match -q '*pattern*' $var; and echo "matched"

# Length
string length "hello"  # 5

# Case conversion
string upper "hello"   # HELLO
string lower "WORLD"   # world

Quoting

# Single quotes - no expansion
echo 'literal $PATH'  # outputs: literal $PATH

# Double quotes - variables/commands expand, but NO word splitting
set var "hello world"
echo "$var"  # one argument: "hello world"
echo $var    # STILL one argument: "hello world" (Fish doesn't split!)

# No quote needed for variables (Fish doesn't word-split)
set files (ls)
for f in $files  # Safe! Each filename is one element
    echo $f
end

Lists and Arrays

# All variables are lists
set mylist one two three

# Access elements (1-indexed!)
echo $mylist[1]      # "one"
echo $mylist[2]      # "two"
echo $mylist[-1]     # "three" (last element)

# Ranges
echo $mylist[1..2]   # "one two"
echo $mylist[2..-1]  # "two three"

# Append
set -a mylist four   # now: one two three four

# Prepend
set -p mylist zero   # now: zero one two three four

# Count
count $mylist        # 5

# Iterate
for item in $mylist
    echo $item
end

Prompt Customization

Fish prompts are functions, not variables:

# ~/.config/fish/functions/fish_prompt.fish
function fish_prompt
    set -l last_status $status

    # Color codes
    set_color blue
    echo -n (prompt_pwd)

    if test $last_status -ne 0
        set_color red
        echo -n " [$last_status]"
    end

    set_color normal
    echo -n ' > '
end

Using Starship (Recommended)

# ~/.config/fish/conf.d/30-starship.fish
if type -q starship
    starship init fish | source
end

Event Handlers

# Run when changing directory
function on_cd --on-variable PWD
    if test -f .envrc
        direnv allow .
    end
end

# Run on Fish start
function on_start --on-event fish_startup
    echo "Welcome to Fish!"
end

Migrating from Bash

Common Bash → Fish Translations

BashFishNotes
VAR=value
set VAR value
Use
-gx
for export
export VAR=value
set -gx VAR value
Global + export
$?
$status
Exit code
$@
$argv
All arguments
$1, $2
$argv[1], $argv[2]
Positional args
${VAR}
$VAR
or
{$VAR}
Braces optional
$((...))
math ...
Arithmetic
[[ ... ]]
test ...
Conditionals
for i in {1..10}
for i in (seq 1 10)
Ranges

No Bash Features in Fish

  • No heredocs (
    <<EOF
    ): Use
    echo
    or files
  • No subshells: Use
    begin; ...; end
    for grouping
  • No
    until
    : Use
    while not
  • No
    [[
    : Use
    test
    or
    [
  • No word splitting: Fish never splits variables on whitespace

Project Integration Patterns

direnv Hook

# ~/.config/fish/conf.d/20-direnv.fish
if type -q direnv
    direnv hook fish | source
end

Nix Integration

# ~/.config/fish/conf.d/10-nix.fish
if test -e /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.fish
    source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.fish
end

Debugging

# Print variable
echo $PATH

# Check if command exists
type -q command; and echo "exists"

# Show function definition
type function_name

# Show all functions
functions

# Show all variables
set

# Debug mode
fish -d 3 -c 'your command'

Common Tasks

Add to PATH

# Temporary (session only)
set -gx PATH $HOME/.local/bin $PATH

# Persistent (universal)
set -U fish_user_paths $HOME/.local/bin $fish_user_paths

Create Wrapper Function

# ~/.config/fish/functions/gs.fish
function gs --wraps='git status' --description 'git status shortcut'
    git status -sb $argv
end

Conditional Config

# Only set if not already set
set -q EDITOR; or set -gx EDITOR nvim

# Platform-specific
if test (uname) = Darwin
    set -gx HOMEBREW_PREFIX /opt/homebrew
end

Output Instructions

When helping with Fish configuration:

  1. Determine what the user needs:

    • New function? → Create in
      ~/.config/fish/functions/name.fish
    • Alias/abbr? → Add to
      conf.d/40-aliases.fish
    • Environment var? → Use
      set -gx
      in appropriate
      conf.d/
      file
    • Prompt? → Suggest Starship or custom
      fish_prompt
      function
  2. Use appropriate tools:

    • Read
      to check existing configs
    • Edit
      to modify existing files
    • Write
      to create new function files
    • Bash(fish:*)
      to test Fish commands
  3. Follow project conventions:

    • Check
      CLAUDE.md
      for project-specific requirements
    • Respect existing
      conf.d/*.fish
      numbering (10, 20, 30, 40...)
    • Use autoloading for functions when possible
    • Guard tools with
      type -q
      checks
  4. Provide working examples:

    • Include complete function definitions
    • Show proper scope flags (
      -l
      ,
      -g
      ,
      -U
      ,
      -x
      )
    • Explain Fish-specific syntax differences from bash
    • Test commands work before suggesting
  5. Explain Fish idioms:

    • Why
      set -gx
      instead of
      export VAR=value
    • Why variables don't need quotes (no word splitting)
    • Why
      $argv
      instead of
      $1
      ,
      $2
      ...
    • Why
      test
      instead of
      [[
    • 1-indexed lists vs 0-indexed in other languages

Reference Documentation

For detailed information, refer to: