Unnamed Skill

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

$ Installieren

git clone https://github.com/wcygan/dotfiles /tmp/dotfiles && cp -r /tmp/dotfiles/.claude/skills/fish-shell-config ~/.claude/skills/dotfiles

// tip: Run this command in your terminal to install the skill


name: fish-shell-config description: 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

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=valueset VAR valueUse -gx for export
export VAR=valueset -gx VAR valueGlobal + export
$?$statusExit code
$@$argvAll 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: