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
$ 설치
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$varbehave identically (already quoted) - Autoloading - functions load on-demand from
~/.config/fish/functions/ - Universal variables - persist across sessions via
-Uflag - Explicit syntax -
set VAR valueinstead ofVAR=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
-llocal: Current block/function only-gglobal: Current session-Uuniversal: Persists across all sessions (stored in~/.config/fish/fish_variables)-xexport: 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
| Bash | Fish | Notes |
|---|---|---|
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): Useechoor files - No subshells: Use
begin; ...; endfor grouping - No
until: Usewhile not - No
[[: Usetestor[ - 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:
-
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 -gxin appropriateconf.d/file - Prompt? → Suggest Starship or custom
fish_promptfunction
- New function? → Create in
-
Use appropriate tools:
Readto check existing configsEditto modify existing filesWriteto create new function filesBash(fish:*)to test Fish commands
-
Follow project conventions:
- Check
CLAUDE.mdfor project-specific requirements - Respect existing
conf.d/*.fishnumbering (10, 20, 30, 40...) - Use autoloading for functions when possible
- Guard tools with
type -qchecks
- Check
-
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
-
Explain Fish idioms:
- Why
set -gxinstead ofexport VAR=value - Why variables don't need quotes (no word splitting)
- Why
$argvinstead of$1,$2... - Why
testinstead of[[ - 1-indexed lists vs 0-indexed in other languages
- Why
Reference Documentation
For detailed information, refer to:
- Fish tutorial: https://fishshell.com/docs/current/tutorial.html
- Language reference: https://fishshell.com/docs/current/language.html
- Command reference: https://fishshell.com/docs/current/commands.html
- Bash migration: https://fishshell.com/docs/current/fish_for_bash_users.html
Repository
