Fish Shell

Essential Fish shell syntax, commands, and scripting techniques

cli
fishshellcliscripting

Installation

macOS

# Install via Homebrew
brew install fish

# Set Fish as default shell
echo /opt/homebrew/bin/fish | sudo tee -a /etc/shells
chsh -s /opt/homebrew/bin/fish

Linux

# Ubuntu/Debian
sudo apt-add-repository ppa:fish-shell/release-3
sudo apt update
sudo apt install fish

# Fedora
sudo dnf install fish

# Arch Linux
sudo pacman -S fish

# Set Fish as default shell
chsh -s /usr/bin/fish

Windows

# Install via Windows Subsystem for Linux (WSL)
sudo apt install fish

# Or use Chocolatey
choco install fish

# Or use winget
winget install Fish.fish

Docker

# Run Fish in Docker
docker run -it ghcr.io/fish-shell/fish

Adding to PATH

# Add single directory to PATH
fish_add_path /usr/local/bin

# Add multiple directories
fish_add_path /usr/local/bin ~/.local/bin

# Prepend to PATH (default)
fish_add_path /opt/custom/bin

# Append to PATH instead of prepend
fish_add_path --append /opt/legacy/bin

# Add only if directory exists
if test -d ~/.cargo/bin
    fish_add_path ~/.cargo/bin
end

# Common additions for development
fish_add_path ~/.local/bin           # Local binaries
fish_add_path ~/.cargo/bin           # Rust cargo
fish_add_path ~/.npm-global/bin      # npm global packages
fish_add_path /usr/local/go/bin      # Go binaries
fish_add_path $HOME/go/bin           # Go workspace
fish_add_path ~/.rbenv/shims         # Ruby rbenv
fish_add_path ~/.pyenv/shims         # Python pyenv

Manual PATH Modification

# Add to PATH manually (old way, not recommended)
set -x PATH $PATH /new/path

# Prepend to PATH
set -x PATH /new/path $PATH

# Add multiple paths
set -x PATH /path/one /path/two $PATH

# Remove from PATH
set -x PATH (string match -v "/old/path" $PATH)

Persistent PATH (in config.fish)

# Add to ~/.config/fish/config.fish for persistence
if status is-interactive
    # Add custom paths
    fish_add_path ~/.local/bin
    fish_add_path ~/.cargo/bin
    fish_add_path ~/bin
end

Variables

Setting Variables

# Set a simple variable
set myvar "hello"

# Set multiple variables at once
set x 1
set y 2
set z 3

# Set a variable with multiple values (list)
set fruits apple banana cherry

# Set a universal variable (persists across sessions)
set -U editor vim

# Set a global variable (available to all functions)
set -g config_path /etc/myapp

# Set a local variable (function scope only)
set -l temp_file /tmp/data.txt

# Export a variable (make it available to subprocesses)
set -x PATH $PATH /usr/local/bin

Accessing Variables

# Access a variable
echo $myvar

# Access list elements (1-indexed)
echo $fruits[1]      # First element: apple
echo $fruits[2..3]   # Range: banana cherry
echo $fruits[-1]     # Last element: cherry

# Get the count of list items
echo (count $fruits)

# Check if variable is set
if set -q myvar
    echo "myvar is set"
end

Unsetting Variables

# Erase a variable
set -e myvar

# Erase a universal variable
set -e -U editor

Functions

Defining Functions

# Simple function
function greet
    echo "Hello, Fish!"
end

# Function with arguments
function greet_user
    echo "Hello, $argv[1]!"
end

# Function with multiple arguments
function add
    math $argv[1] + $argv[2]
end

# Function with description (shows in help)
function deploy --description "Deploy the application"
    echo "Deploying..."
    # deployment logic here
end

# Function with argument names (documentation only)
function create_user --argument name email
    echo "Creating user: $name ($email)"
end

Function Arguments

# Access all arguments
function print_args
    echo "All arguments: $argv"
end

# Access specific arguments
function greet_full
    echo "Hello, $argv[1] $argv[2]!"
end

# Count arguments
function arg_count
    echo "You passed" (count $argv) "arguments"
end

# Check if arguments exist
function require_arg
    if test (count $argv) -eq 0
        echo "Error: No arguments provided"
        return 1
    end
    echo "Processing: $argv[1]"
end

Autoloading Functions

# Save function to file for autoloading
# File: ~/.config/fish/functions/myfunction.fish
function myfunction
    echo "This function auto-loads!"
end

# List all functions
functions

# Show function definition
functions myfunction

# Erase a function
functions -e myfunction

Conditionals

If Statements

# Simple if
if test $status -eq 0
    echo "Success"
end

# If-else
if test -f ~/.config/fish/config.fish
    echo "Config exists"
else
    echo "Config not found"
end

# If-else if-else
if test $count -gt 10
    echo "More than 10"
else if test $count -gt 5
    echo "More than 5"
else
    echo "5 or less"
end

# Using square brackets (shorthand for test)
if [ $USER = "root" ]
    echo "Running as root"
end

Test Operators

# String comparisons
if test $name = "Alice"
    echo "Hello Alice"
end

if test $name != "Bob"
    echo "Not Bob"
end

# Numeric comparisons
if test $age -gt 18      # Greater than
    echo "Adult"
end

if test $age -lt 13      # Less than
    echo "Child"
end

if test $age -ge 18      # Greater than or equal
    echo "Voting age"
end

if test $age -le 65      # Less than or equal
    echo "Working age"
end

if test $age -eq 30      # Equal
    echo "Exactly 30"
end

if test $age -ne 25      # Not equal
    echo "Not 25"
end

# File tests
if test -f /path/to/file       # File exists and is regular file
    echo "File exists"
end

if test -d /path/to/dir        # Directory exists
    echo "Directory exists"
end

if test -e /path/to/something  # Path exists (file or dir)
    echo "Path exists"
end

if test -r /path/to/file       # Readable
    echo "Can read"
end

if test -w /path/to/file       # Writable
    echo "Can write"
end

if test -x /path/to/file       # Executable
    echo "Can execute"
end

Logical Operators

# AND operator
if test $age -gt 18; and test $age -lt 65
    echo "Working age"
end

# OR operator
if test $day = "Saturday"; or test $day = "Sunday"
    echo "Weekend!"
end

# NOT operator
if not test -f ~/.ssh/id_rsa
    echo "SSH key not found"
end

# Combining operators
if test $status -eq 0; and not test -z "$output"
    echo "Command succeeded with output"
end

Switch Statements

# Switch case
switch $animal
    case cat
        echo "Meow"
    case dog
        echo "Woof"
    case bird
        echo "Tweet"
    case '*'
        echo "Unknown animal"
end

# Multiple patterns per case
switch $extension
    case jpg jpeg png gif
        echo "Image file"
    case mp4 avi mkv
        echo "Video file"
    case mp3 wav flac
        echo "Audio file"
    case '*'
        echo "Unknown file type"
end

Loops

For Loops

# Loop over list
for item in apple banana cherry
    echo $item
end

# Loop over variable list
set fruits apple banana cherry
for fruit in $fruits
    echo "I like $fruit"
end

# Loop over command output
for file in *.txt
    echo "Processing $file"
end

# Loop over range (using seq)
for i in (seq 1 5)
    echo "Number: $i"
end

# Loop with index
for i in (seq (count $items))
    echo "Item $i: $items[$i]"
end

While Loops

# Basic while loop
set i 1
while test $i -le 5
    echo $i
    set i (math $i + 1)
end

# While reading lines from file
while read -l line
    echo "Line: $line"
end < input.txt

# Infinite loop with break
while true
    echo "Press Ctrl+C to stop"
    sleep 1
end

Loop Control

# Break - exit loop
for i in (seq 1 10)
    if test $i -eq 5
        break
    end
    echo $i
end

# Continue - skip to next iteration
for i in (seq 1 10)
    if test (math $i % 2) -eq 0
        continue  # Skip even numbers
    end
    echo $i
end

Command Substitution

# Capture command output
set current_dir (pwd)
set files (ls)
set date_str (date +%Y-%m-%d)

# Use in strings
echo "Today is $date_str"

# Use in conditions
if test (whoami) = "root"
    echo "Running as root"
end

# Nested substitution
set user_home (echo ~(whoami))

String Manipulation

String Operations

# String length
set name "Alice"
echo (string length $name)  # 5

# Substring
set str "Hello World"
echo (string sub -s 1 -l 5 $str)  # Hello
echo (string sub -s 7 $str)        # World

# Uppercase/lowercase
echo (string upper "hello")  # HELLO
echo (string lower "WORLD")  # world

# Trim whitespace
set text "  hello  "
echo (string trim $text)      # hello
echo (string trim -l $text)   # "hello  " (left trim)
echo (string trim -r $text)   # "  hello" (right trim)

String Matching

# Match pattern
if string match -q "*.txt" $filename
    echo "Text file"
end

# Match with regex
if string match -qr '^\d+$' $input
    echo "It's a number"
end

# Replace string
set path "/home/user/documents"
echo (string replace "/home/user" "~" $path)  # ~/documents

# Replace all occurrences
set text "foo bar foo"
echo (string replace -a "foo" "baz" $text)  # baz bar baz

# Split string
set csv "apple,banana,cherry"
set items (string split "," $csv)
echo $items[1]  # apple

# Join strings
set items apple banana cherry
echo (string join ", " $items)  # apple, banana, cherry

Lists

List Operations

# Create list
set colors red green blue

# Append to list
set colors $colors yellow

# Prepend to list
set colors orange $colors

# Access elements
echo $colors[1]      # First element
echo $colors[-1]     # Last element
echo $colors[2..4]   # Range

# List length
echo (count $colors)

# Check if item in list
if contains red $colors
    echo "Red is in the list"
end

# Remove duplicates
set numbers 1 2 2 3 3 3 4
set unique (echo $numbers | tr ' ' '\n' | sort -u)

# Reverse list
set reversed $colors[-1..1]

Input/Output

Reading Input

# Read user input
read -P "Enter your name: " name
echo "Hello, $name"

# Read with prompt and local variable
read -l -P "Enter password: " password

# Read line by line from file
while read -l line
    echo "Line: $line"
end < file.txt

# Read from stdin
cat file.txt | while read -l line
    echo "Processing: $line"
end

Output

# Print to stdout
echo "Normal output"

# Print to stderr
echo "Error message" >&2

# Suppress output
command > /dev/null 2>&1

# Append to file
echo "Log entry" >> logfile.txt

# Write to file (overwrite)
echo "New content" > file.txt

Pipes and Redirection

# Pipe output to another command
ls -l | grep ".txt"

# Redirect stdout to file
echo "Hello" > output.txt

# Redirect stderr to file
command 2> errors.txt

# Redirect both stdout and stderr
command > output.txt 2>&1

# Append to file
echo "More data" >> output.txt

# Pipe stderr
command 2>&1 | grep "error"

Process Substitution

# Background job
sleep 10 &

# List background jobs
jobs

# Wait for job to complete
wait

# Kill job
kill %1

# Command chaining
command1; and command2    # Run command2 only if command1 succeeds
command1; or command2     # Run command2 only if command1 fails

Environment Variables

# Set environment variable
set -x DATABASE_URL "postgresql://localhost/mydb"

# Set PATH
set -x PATH $PATH /usr/local/bin

# Remove from PATH
set -x PATH (string match -v "/old/path" $PATH)

# Set for single command
env VAR=value command

# View all environment variables
env

# View specific variable
echo $PATH

Configuration

Config File

# Main config: ~/.config/fish/config.fish

# Set greeting
set fish_greeting "Welcome to Fish!"

# Or disable greeting
set fish_greeting

# Add to PATH
fish_add_path /usr/local/bin
fish_add_path ~/.local/bin

# Set environment variables
set -x EDITOR vim
set -x VISUAL code

Aliases

# Create alias (use abbreviation instead)
abbr -a ll 'ls -lah'
abbr -a gst 'git status'
abbr -a gco 'git checkout'

# List abbreviations
abbr -l

# Erase abbreviation
abbr -e ll

Prompt

# Custom prompt function
function fish_prompt
    set_color green
    echo -n (whoami)
    set_color normal
    echo -n '@'
    set_color blue
    echo -n (hostname)
    set_color normal
    echo -n ':'
    set_color yellow
    echo -n (prompt_pwd)
    set_color normal
    echo -n '> '
end

Useful Commands

# Change directory
cd /path/to/directory

# Go to home
cd ~

# Go to previous directory
cd -

# Push directory to stack
pushd /path/to/dir

# Pop directory from stack
popd

# List directory stack
dirs

File Operations

# Copy files
cp source.txt destination.txt

# Move/rename files
mv old.txt new.txt

# Remove files
rm file.txt

# Remove directory recursively
rm -rf directory/

# Create directory
mkdir new_folder

# Create nested directories
mkdir -p path/to/nested/folder

# Find files
find . -name "*.fish"

# Search file contents
grep "pattern" file.txt

# Search recursively
grep -r "pattern" directory/

Math Operations

# Basic arithmetic
math 5 + 3        # 8
math 10 - 4       # 6
math 6 '*' 7      # 42 (quote * to prevent glob)
math 15 / 3       # 5
math 17 % 5       # 2 (modulo)

# Floating point
math 10 / 3       # 3.333333

# Power
math "2 ^ 8"      # 256

# Using in variables
set result (math $a + $b)

# Increment variable
set count (math $count + 1)

Event Handlers

# Run when variable changes
function on_pwd_change --on-variable PWD
    echo "Directory changed to $PWD"
end

# Run on Fish exit
function on_exit --on-event fish_exit
    echo "Goodbye!"
end

# Run on prompt
function before_prompt --on-event fish_prompt
    # Do something before each prompt
end

Completions

# Define custom completion
complete -c mycommand -s h -l help -d "Show help"
complete -c mycommand -s v -l version -d "Show version"
complete -c mycommand -s o -l output -r -d "Output file"

# File completion
complete -c mycommand -a '(__fish_complete_path)'

# Disable file completion
complete -c mycommand -f