Fish shell scripting - functions, abbreviations, completions, and configuration When user works with .fish files, mentions Fish shell, fish config, Fisher plugins, or Fish scripting patterns
bind ctrl-right instead of escape sequences), xterm modifyOtherKeys and kitty keyboard protocol supportabbr --command git co checkoutfish_should_add_to_history function for selective exclusionstring match --max-matches and set --no-event flags{ echo 1; echo 2 } syntaxfish_transient_prompt function for simplified prompt after executionstring pad --center optionfish_tab_title function for separate tab titles[light] and [dark] sections in theme filesd3w)catppuccin-* color themesset_color strikethrough modifierFish (Friendly Interactive SHell) is a modern interactive shell focused on user experience. It provides syntax highlighting, autosuggestions, and tab completions out of the box. Fish intentionally breaks POSIX compatibility in favor of cleaner, more discoverable syntax.
Key design principles:
# Fish uses set, not VAR=value
set name "world"
set -gx PATH /usr/local/bin $PATH # global + exported
set -l local_var "temporary" # local scope
set -U EDITOR vim # universal (persists across sessions)
set -e var_name # erase variable
-U): Shared across all sessions, persisted to disk-g): Current session only-f): Current function-l): Current block# Fish uses (command) or $(command), NOT backticks
set files (ls)
echo "Current dir: $(pwd)"
# Output splits on newlines only (not whitespace like bash)
# Use quotes to prevent splitting
set content "$(cat file.txt)"
# Bash: diff <(cmd1) <(cmd2)
# Fish: use psub
diff (cmd1 | psub) (cmd2 | psub)
# if/else if/else/end (no then/fi)
if test -f /etc/os-release
cat /etc/os-release
else if test -f /etc/issue
cat /etc/issue
else
echo "Unknown OS"
end
# switch/case/end (no esac, no fallthrough)
switch (uname)
case Linux
echo "Linux"
case Darwin
echo "macOS"
case '*'
echo "Other"
end
# for/end (no do/done)
for file in *.txt
echo $file
end
# while/end
while read -l line
echo "Line: $line"
end < input.txt
# All variables are lists. 1-indexed, negative indexing supported
set colors red green blue
echo $colors[1] # red
echo $colors[-1] # blue
echo $colors[2..3] # green blue
echo (count $colors) # 3
# PATH variables auto-split on colons
set -gx PATH /usr/local/bin /usr/bin /bin
# Use the string builtin (no ${var%pattern} parameter expansion)
string length "hello" # 5
string upper "hello" # HELLO
string replace "old" "new" "old text" # new text
string split "," "a,b,c" # a\nb\nc
string match -r '(\d+)' "file42.txt" # 42
string trim " hello " # hello
string sub -s 2 -l 3 "hello" # ell
# Use math builtin (no $(( )) or let)
math 2 + 2 # 4
math "10 / 3" # 3.333333 (floating point by default)
math "sqrt(16)" # 4
set result (math "$x * 2")
| Bash | Fish |
|---|---|
$? | $status |
$@, $* | $argv |
$$ | $fish_pid |
$# | (count $argv) |
$! | $last_pid |
$0 | (status filename) |
printf '%s\n' "line1" "line2" or multi-line strings[[: use test or [ onlybegin; end for grouping, set -l for scopingexport: use set -gxsource ~/.bashrc: use source ~/.config/fish/config.fish? glob deprecated; use * or disable with qmark-noglob# Define a function
function greet -d "Greet someone"
echo "Hello, $argv[1]!"
end
# Function with argument names
function mkcd -a dir -d "Create and enter directory"
mkdir -p $dir && cd $dir
end
# Function wrapping a command (inherit completions)
function ls --wraps ls -d "ls with color"
command ls --color=auto $argv
end
# Event handlers
function on_pwd_change --on-variable PWD
echo "Changed to $PWD"
end
function on_exit --on-event fish_exit
echo "Goodbye!"
end
# Save function to autoload file
funcsave greet # saves to ~/.config/fish/functions/greet.fish
# Simple abbreviation
abbr -a gco git checkout
abbr -a gst git status
# Position: expand anywhere (not just as command)
abbr -a --position anywhere -- -C --color
# Command-specific (Fish 4.0+)
abbr --command git co checkout
abbr --command git br branch
# With cursor positioning
abbr -a L --position anywhere --set-cursor "| less"
# Function-based expansion
abbr -a !! --position anywhere --function last_history_item
# Regex-based
abbr -a dotenv --regex '\.env.*' --function edit_with_caution
# Basic completion for a command
complete -c mycommand -s h -l help -d "Show help"
complete -c mycommand -s v -l verbose -d "Verbose output"
# Require a parameter
complete -c mycommand -s o -l output -r -d "Output file" -F
# Exclusive (require param, no files)
complete -c mycommand -s f -l format -x -a "json yaml toml" -d "Output format"
# Conditional completions
complete -c git -n "__fish_use_subcommand" -a checkout -d "Switch branches"
complete -c git -n "__fish_seen_subcommand_from checkout" -a "(git branch --format='%(refname:short)')" -d "Branch"
# Disable file completions globally
complete -c mycommand -f
# Wrap another command's completions
complete -c hub -w git
~/.config/fish/
config.fish # Main config (runs on every shell start)
conf.d/ # Modular config snippets (sourced alphabetically)
abbr.fish
path.fish
env.fish
functions/ # Autoloaded functions (one per file)
fish_prompt.fish
fish_right_prompt.fish
mkcd.fish
completions/ # Custom completions (one per command)
mycommand.fish
themes/ # Color themes (.theme files)
fish_plugins # Fisher plugin list
fish_variables # Universal variables (auto-managed, do not edit)
conf.d/ directories (system, then user) in alphabetical orderconfig.fishfish_prompt -- left promptfish_right_prompt -- right promptfish_mode_prompt -- vi mode indicatorfish_transient_prompt -- simplified prompt shown after command execution (Fish 4.1+)fish_greeting -- message shown on shell start (set to empty to disable)# Emacs mode (default)
fish_default_key_bindings
# Vi mode
fish_vi_key_bindings
# Custom bindings
bind ctrl-r 'commandline -f history-pager'
bind \t complete
bind ctrl-e 'edit_command_buffer'
# Vi mode insert-mode binding
bind --mode insert ctrl-c 'commandline -r ""'
read -l -P "Name: " name
read -l -s -P "Password: " password # silent input
read -l -P "Continue? [Y/n] " -c "Y" answer
read -l -n 1 char # single character
read -la lines < file.txt # read file into list
# Read from pipe
echo "hello world" | read -l first rest
echo $first # hello
echo $rest # world
status is-interactive # true in interactive shell
status is-login # true in login shell
status is-command-substitution # true inside $(...)
status filename # current script path
status function # current function name
status line-number # current line number
status current-command # name of currently running command
status features # list enabled features
status test-feature qmark-noglob # check feature flag
if contains "blue" $colors
echo "Found blue"
end
set idx (contains -i "green" $colors) # get index
type --short ls # alias, builtin, function, or file
type --path ls # file path of command
command -sq docker # check if command exists (silent, quiet)
builtin -n # list all builtins
functions # list all defined functions
functions --names # names only
functions myfunction # print source of function
source file.fish
source (command which env_setup.fish)
# Source with arguments
source script.fish arg1 arg2 # $argv available in script
emit my_custom_event "arg1" "arg2"
function handle_event --on-event my_custom_event
echo "Event received: $argv"
end
# At top of config.fish
if not status is-interactive
return
end
# Only add to PATH if directory exists
for dir in ~/.local/bin ~/.cargo/bin ~/go/bin
test -d $dir; and fish_add_path $dir
end
function git --wraps git -d "Git with default options"
command git -c color.ui=always $argv
end
function retry -a max_attempts cmd
set -l attempt 1
while test $attempt -le $max_attempts
eval $cmd; and return 0
set attempt (math $attempt + 1)
sleep 1
end
return 1
end
# Fish has no VAR=value command syntax. Use env or begin/end block:
env PGPASSWORD=secret psql -U user db
# Or scope with begin/end
begin
set -lx NODE_ENV production
npm start
end
# Trace execution
set fish_trace 1
some_command
set fish_trace 0
# Profile script performance
fish --profile profile.log -c 'source script.fish'
# Check if interactive/login
status is-interactive
status is-login
# Print function source
functions myfunction
type myfunction
# Debug completions
complete -C "mycommand " # show what would complete
# List all key bindings
bind # show all active bindings
bind --mode insert # vi insert mode bindings
For detailed reference material, see:
references/fish-syntax.md -- Variables, control flow, strings, lists, pipes, mathreferences/completions-functions.md -- Writing completions, functions, abbreviations, event handlersreferences/plugins-config.md -- Fisher, popular plugins, config patterns, prompt customization