Skip to content

Fish - Friendly Interactive Shell

Fish (Friendly Interactive Shell) is a smart and user-friendly command line shell designed for interactive use, usability, and discoverability. Created by Axel Liljencrantz in 2005, Fish takes a different approach from traditional shells by prioritizing user experience and providing sensible defaults out of the box. Unlike POSIX-compliant shells, Fish features syntax highlighting, autosuggestions, tab completions that work without configuration, and a clean, modern syntax that aims to be more intuitive and less error-prone than traditional shell languages.

Installation and Setup

Installing Fish Shell

fish
# Ubuntu/Debian
sudo apt update && sudo apt install fish

# CentOS/RHEL/Fedora
sudo dnf install fish

# macOS (using Homebrew)
brew install fish

# Arch Linux
sudo pacman -S fish

# FreeBSD
pkg install fish

# From source (latest version)
git clone https://github.com/fish-shell/fish-shell.git
cd fish-shell
cmake .
make
sudo make install

Setting Fish as Default Shell

fish
# Check Fish installation
which fish
/usr/bin/fish

# Add Fish to available shells
echo /usr/bin/fish | sudo tee -a /etc/shells

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

# Verify change (restart terminal)
echo $SHELL

First-Time Setup

fish
# Start Fish shell
fish

# Fish will create configuration directory automatically
# ~/.config/fish/

# Run Fish configuration wizard
fish_config

# This opens a web-based configuration interface
# Accessible at http://localhost:8000

Basic Configuration Structure

fish
# Fish configuration files
~/.config/fish/config.fish       # Main configuration file
~/.config/fish/functions/         # Custom functions directory
~/.config/fish/completions/       # Custom completions directory
~/.config/fish/conf.d/            # Additional configuration files

# Create basic config.fish
mkdir -p ~/.config/fish
cat > ~/.config/fish/config.fish << 'EOF'
# Fish configuration

# Set environment variables
set -gx EDITOR vim
set -gx BROWSER firefox

# Add to PATH
set -gx PATH $HOME/bin $PATH
set -gx PATH $HOME/.local/bin $PATH

# Aliases
alias ll 'ls -alF'
alias la 'ls -A'
alias l 'ls -CF'

# Custom greeting
set fish_greeting "Welcome to Fish Shell!"
EOF

Fish Syntax and Language Features

Variables and Scope

fish
# Variable assignment (no $ for assignment)
set name "John Doe"
set age 30
set path "/home/user"

# Using variables ($ required for expansion)
echo $name
echo "Hello, $name"
echo "Age: $age"

# Variable scopes
set -l local_var "local"          # Local to current scope
set -g global_var "global"        # Global to current session
set -U universal_var "universal"  # Universal across all sessions
set -x exported_var "exported"    # Exported to child processes

# Multiple assignment
set fruits apple banana orange
echo $fruits[1]                   # apple (1-indexed)
echo $fruits[2]                   # banana
echo $fruits[-1]                  # orange (last element)

# Array operations
set fruits $fruits grape          # Append
set fruits[2] kiwi               # Replace element
set -e fruits[1]                 # Erase element

Command Substitution

fish
# Command substitution using parentheses
set current_date (date)
set file_count (ls | wc -l)
set user_home (eval echo ~$USER)

# Nested command substitution
echo "Today is "(date +%A)", "(date +%B)" "(date +%d)

# Store command output in variable
set git_branch (git branch --show-current 2>/dev/null)
if test -n "$git_branch"
    echo "Current branch: $git_branch"
end

String Manipulation

fish
# String operations
set string "Hello, World!"
echo (string length "$string")    # String length
echo (string sub -s 1 -l 5 "$string")  # Substring (Hello)
echo (string replace "Hello" "Hi" "$string")  # Replace
echo (string upper "$string")     # Uppercase
echo (string lower "$string")     # Lowercase

# String splitting and joining
set words (string split " " "$string")
echo $words[1]                    # Hello,
set rejoined (string join "-" $words)
echo $rejoined                    # Hello,-World!

# Pattern matching
if string match -q "Hello*" "$string"
    echo "String starts with Hello"
end

# Regular expressions
if string match -qr "W\w+d" "$string"
    echo "String contains word starting with W and ending with d"
end

Conditional Statements

fish
# if-then-else
if test $age -gt 18
    echo "Adult"
else if test $age -eq 18
    echo "Just turned adult"
else
    echo "Minor"
end

# Test conditions
if test -f "file.txt"             # File exists
    echo "File exists"
end

if test -d "directory"            # Directory exists
    echo "Directory exists"
end

if test "$var" = "value"          # String equality
    echo "Variable equals value"
end

if test $num -eq 10               # Numeric equality
    echo "Number is 10"
end

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

if test $status -eq 0; or test $force = "true"
    echo "Success or forced"
end

if not test -f "file.txt"
    echo "File does not exist"
end

# Switch statement
switch $file_extension
    case "txt"
        echo "Text file"
    case "jpg" "png" "gif"
        echo "Image file"
    case "*"
        echo "Unknown file type"
end

Loops and Iteration

fish
# for loop
for i in (seq 1 10)
    echo "Number: $i"
end

for file in *.txt
    echo "Processing: $file"
end

for item in $array
    echo "Item: $item"
end

# while loop
set counter 1
while test $counter -le 10
    echo "Counter: $counter"
    set counter (math $counter + 1)
end

# Loop control
for i in (seq 1 10)
    if test $i -eq 5
        continue    # Skip iteration
    end
    if test $i -eq 8
        break      # Exit loop
    end
    echo $i
end

Functions and Scripting

Function Definition

fish
# Basic function
function greet
    echo "Hello, $argv[1]!"
end

# Function with description
function greet --description "Greet a user"
    echo "Hello, $argv[1]!"
end

# Function with argument validation
function calculate_sum --description "Calculate sum of two numbers"
    if test (count $argv) -ne 2
        echo "Usage: calculate_sum <num1> <num2>"
        return 1
    end
    
    math $argv[1] + $argv[2]
end

# Function with local variables
function process_file --description "Process a file"
    set -l filename $argv[1]
    set -l line_count (wc -l < "$filename")
    echo "File $filename has $line_count lines"
end

Advanced Function Features

fish
# Function with options
function my_ls --description "Enhanced ls with options"
    argparse 'l/long' 'a/all' 'h/help' -- $argv
    or return

    if set -q _flag_help
        echo "Usage: my_ls [-l|--long] [-a|--all] [directory]"
        return
    end

    set -l ls_args
    if set -q _flag_long
        set ls_args $ls_args -l
    end
    if set -q _flag_all
        set ls_args $ls_args -a
    end

    ls $ls_args $argv
end

# Function with completion
function mycommand --description "Custom command with completion"
    switch $argv[1]
        case "start"
            echo "Starting service"
        case "stop"
            echo "Stopping service"
        case "status"
            echo "Service status"
        case "*"
            echo "Usage: mycommand {start|stop|status}"
    end
end

# Save function permanently
funcsave greet
funcsave calculate_sum

Error Handling

fish
# Function with error handling
function safe_copy --description "Safe file copy with error handling"
    if test (count $argv) -ne 2
        echo "Error: Exactly two arguments required" >&2
        return 1
    end

    set -l source $argv[1]
    set -l dest $argv[2]

    if not test -f "$source"
        echo "Error: Source file '$source' does not exist" >&2
        return 1
    end

    if test -f "$dest"
        echo "Warning: Destination file '$dest' already exists"
        read -P "Overwrite? (y/N): " -l confirm
        if test "$confirm" != "y"
            echo "Copy cancelled"
            return 1
        end
    end

    if cp "$source" "$dest"
        echo "Successfully copied '$source' to '$dest'"
    else
        echo "Error: Failed to copy file" >&2
        return 1
    end
end

Interactive Features

Autosuggestions

fish
# Autosuggestions are enabled by default
# Type a command and Fish will suggest completions based on:
# - Command history
# - Valid file paths
# - Command completions

# Accept suggestion: Right arrow or Ctrl+F
# Accept single word: Alt+Right arrow or Alt+F
# Dismiss suggestion: Escape

# Configure autosuggestion color
set -g fish_color_autosuggestion 555

Tab Completions

fish
# Tab completions work automatically for:
# - Commands in PATH
# - File and directory names
# - Command-specific options and arguments

# Custom completion for your function
complete -c mycommand -a "start stop status restart" -d "Service commands"
complete -c mycommand -s h -l help -d "Show help"
complete -c mycommand -s v -l verbose -d "Verbose output"

# File-based completion
complete -c myapp -a "(__fish_complete_suffix .conf)" -d "Configuration files"

# Conditional completion
complete -c git -n "__fish_git_needs_command" -a "add commit push pull"
complete -c git -n "__fish_git_using_command add" -a "(__fish_git_modified_files)"

Syntax Highlighting

fish
# Syntax highlighting is automatic and includes:
# - Valid/invalid commands (green/red)
# - Strings and quotes
# - Variables and expansions
# - Comments

# Customize syntax highlighting colors
set -g fish_color_command blue
set -g fish_color_param cyan
set -g fish_color_redirection yellow
set -g fish_color_comment brblack
set -g fish_color_error red
set -g fish_color_escape bryellow
set -g fish_color_operator green
set -g fish_color_quote yellow
set -g fish_color_valid_path --underline
fish
# History search (automatic)
# Type partial command and use Up/Down arrows

# History search with specific text
# Type text and press Ctrl+R for reverse search

# History commands
history                           # Show all history
history search "git"              # Search history for "git"
history delete --prefix "rm"      # Delete commands starting with "rm"
history clear                     # Clear all history

# Configure history
set -g fish_history_max 10000     # Maximum history entries

Configuration and Customization

Environment Variables

fish
# Set environment variables
set -gx EDITOR vim
set -gx BROWSER firefox
set -gx PAGER less

# PATH manipulation
set -gx PATH $HOME/bin $PATH
set -gx PATH $HOME/.local/bin $PATH
set -gx PATH /usr/local/bin $PATH

# Remove from PATH
set -l index (contains -i /unwanted/path $PATH)
if test $index -gt 0
    set -e PATH[$index]
end

# Conditional environment variables
if test -d "$HOME/.cargo/bin"
    set -gx PATH $HOME/.cargo/bin $PATH
end

# Platform-specific variables
switch (uname)
    case Darwin
        set -gx HOMEBREW_PREFIX /opt/homebrew
    case Linux
        set -gx XDG_CONFIG_HOME $HOME/.config
end

Aliases and Abbreviations

fish
# Aliases (expanded when defined)
alias ll 'ls -alF'
alias la 'ls -A'
alias l 'ls -CF'
alias grep 'grep --color=auto'

# Abbreviations (expanded when typed)
abbr -a g git
abbr -a gc 'git commit'
abbr -a gp 'git push'
abbr -a gl 'git log --oneline'
abbr -a gst 'git status'

# Conditional abbreviations
if command -v docker >/dev/null
    abbr -a d docker
    abbr -a dc 'docker-compose'
    abbr -a dps 'docker ps'
end

# List and manage abbreviations
abbr -l                           # List all abbreviations
abbr -e gc                        # Erase abbreviation

Prompt Customization

fish
# Simple 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

# Advanced prompt with Git integration
function fish_prompt
    set -l last_status $status
    
    # User and host
    set_color green
    echo -n (whoami)
    set_color normal
    echo -n "@"
    set_color blue
    echo -n (hostname)
    set_color normal
    
    # Current directory
    echo -n ":"
    set_color yellow
    echo -n (prompt_pwd)
    set_color normal
    
    # Git information
    if git rev-parse --git-dir >/dev/null 2>&1
        set -l branch (git branch --show-current 2>/dev/null)
        if test -n "$branch"
            echo -n " ("
            set_color cyan
            echo -n "$branch"
            set_color normal
            
            # Check for changes
            if not git diff --quiet 2>/dev/null
                set_color red
                echo -n "*"
                set_color normal
            end
            echo -n ")"
        end
    end
    
    # Prompt symbol based on last command status
    if test $last_status -eq 0
        set_color green
        echo -n " ❯ "
    else
        set_color red
        echo -n " ❯ "
    end
    set_color normal
end

# Right prompt
function fish_right_prompt
    set_color brblack
    echo -n (date "+%H:%M:%S")
    set_color normal
end

Theme and Color Configuration

fish
# Use fish_config for GUI configuration
fish_config

# Or set colors manually
set -g fish_color_normal normal
set -g fish_color_command blue
set -g fish_color_quote yellow
set -g fish_color_redirection cyan
set -g fish_color_end green
set -g fish_color_error red
set -g fish_color_param cyan
set -g fish_color_comment brblack
set -g fish_color_match --background=brblue
set -g fish_color_selection white --bold --background=brblack
set -g fish_color_search_match bryellow --background=brblack
set -g fish_color_history_current --bold
set -g fish_color_operator green
set -g fish_color_escape bryellow
set -g fish_color_cwd green
set -g fish_color_cwd_root red
set -g fish_color_valid_path --underline
set -g fish_color_autosuggestion 555
set -g fish_color_user brgreen
set -g fish_color_host normal
set -g fish_color_cancel -r
set -g fish_pager_color_completion normal
set -g fish_pager_color_description B3A06D yellow
set -g fish_pager_color_prefix white --bold --underline
set -g fish_pager_color_progress brwhite --background=cyan

Package Management and Plugins

Fisher Plugin Manager

fish
# Install Fisher
curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher

# Install plugins
fisher install jorgebucaran/nvm.fish
fisher install PatrickF1/fzf.fish
fisher install franciscolourenco/done
fisher install jethrokuan/z

# List installed plugins
fisher list

# Update plugins
fisher update

# Remove plugin
fisher remove jorgebucaran/nvm.fish
fish
# z - Directory jumping
fisher install jethrokuan/z
# Usage: z partial_directory_name

# fzf integration
fisher install PatrickF1/fzf.fish
# Provides Ctrl+R for history search, Ctrl+Alt+F for file search

# done - Desktop notifications
fisher install franciscolourenco/done
# Notifies when long-running commands complete

# nvm for Node.js
fisher install jorgebucaran/nvm.fish
nvm install node
nvm use node

# autopair - Automatic bracket pairing
fisher install jorgebucaran/autopair.fish

# bass - Run Bash utilities in Fish
fisher install edc/bass
bass source ~/.bashrc

Manual Plugin Installation

fish
# Create functions directory
mkdir -p ~/.config/fish/functions

# Download and install plugin manually
curl -Lo ~/.config/fish/functions/fisher.fish --create-dirs https://git.io/fisher

# Install from local directory
git clone https://github.com/user/plugin.git
cp plugin/*.fish ~/.config/fish/functions/

Advanced Features and Tips

Math Operations

fish
# Math command for calculations
math 5 + 3                       # 8
math 10 \* 2                     # 20 (escape * in some contexts)
math "10 * 2"                    # 20
math 20 / 4                      # 5
math 17 % 5                      # 2

# Math with variables
set num1 10
set num2 5
math $num1 + $num2               # 15

# Advanced math functions
math "sqrt(16)"                  # 4
math "sin(3.14159/2)"           # 1
math "log(10)"                  # 2.30259
math "pow(2, 8)"                # 256

# Floating point precision
math -s2 10 / 3                 # 3.33 (2 decimal places)

File Operations and Globbing

fish
# Basic globbing
ls *.txt                        # All .txt files
ls **/*.py                      # All .py files recursively
ls file?.txt                    # file1.txt, file2.txt, etc.

# Advanced globbing patterns
ls *.(txt|md|rst)              # Files with specific extensions
ls file[1-9].txt               # file1.txt through file9.txt

# File test operations
if test -f "file.txt"
    echo "File exists"
end

if test -d "directory"
    echo "Directory exists"
end

if test -x "script.sh"
    echo "File is executable"
end

# File manipulation
cp source.txt destination.txt
mv old_name.txt new_name.txt
rm unwanted_file.txt
mkdir new_directory
rmdir empty_directory

Process Management

fish
# Background jobs
command &                       # Run in background
jobs                           # List active jobs
fg %1                          # Bring job 1 to foreground
bg %1                          # Send job 1 to background
kill %1                        # Kill job 1

# Process information
ps aux                         # List all processes
pgrep firefox                  # Find process by name
pkill firefox                  # Kill process by name

# Disown processes
command &
disown                         # Detach from shell

Input/Output Redirection

fish
# Output redirection
command > file.txt             # Redirect stdout to file
command >> file.txt            # Append stdout to file
command 2> error.log           # Redirect stderr to file
command &> output.log          # Redirect both stdout and stderr

# Input redirection
command < input.txt            # Read input from file

# Pipes
ls -l | grep "txt"             # Pipe output to grep
ps aux | grep "firefox" | wc -l  # Count firefox processes

# Tee command
command | tee file.txt         # Write to file and stdout

Scripting Best Practices

Script Structure

fish
#!/usr/bin/env fish
# Script description
# Author: Your Name
# Date: YYYY-MM-DD

# Function definitions
function usage
    echo "Usage: $argv[0] <option> <argument>"
    echo "Options:"
    echo "  -h, --help     Show this help"
    echo "  -v, --verbose  Verbose output"
end

function main
    # Parse arguments
    argparse 'h/help' 'v/verbose' -- $argv
    or return

    if set -q _flag_help
        usage
        return 0
    end

    if set -q _flag_verbose
        set -g verbose true
    end

    # Main script logic here
    echo "Script execution completed"
end

# Call main function with all arguments
main $argv

Error Handling

fish
# Check command success
if command_that_might_fail
    echo "Command succeeded"
else
    echo "Command failed with status $status"
    exit 1
end

# Validate arguments
function validate_file
    set -l file $argv[1]
    if not test -f "$file"
        echo "Error: File '$file' does not exist" >&2
        return 1
    end
    return 0
end

# Use in script
if not validate_file "important.txt"
    exit 1
end

Performance Considerations

fish
# Use built-in string operations instead of external commands
set result (string replace "old" "new" $text)  # Instead of sed
set length (string length $text)               # Instead of wc

# Avoid unnecessary command substitutions
if test -f "file.txt"                         # Instead of if test (ls file.txt)
    echo "File exists"
end

# Use arrays efficiently
set files *.txt
for file in $files                            # Instead of for file in (ls *.txt)
    process_file $file
end

Debugging and Troubleshooting

Debug Mode

fish
# Run script with debug output
fish -d 3 script.fish

# Enable debug in script
set fish_trace 1
# Your commands here
set fish_trace 0

# Function debugging
function debug_function
    echo "Function called with arguments: $argv" >&2
    echo "Current directory: "(pwd) >&2
    echo "Status: $status" >&2
end

Common Issues and Solutions

fish
# Issue: Command not found
# Solution: Check PATH and command existence
if not command -v mycommand >/dev/null
    echo "mycommand is not installed or not in PATH"
    exit 1
end

# Issue: Variable not expanding
# Solution: Use proper Fish syntax
set var "value"
echo $var                      # Correct
echo "$var"                    # Also correct
echo '$var'                    # Wrong - literal string

# Issue: Function not found
# Solution: Check function definition and loading
functions myfunction           # Check if function exists
funcsave myfunction           # Save function permanently

Performance Profiling

fish
# Time command execution
time command

# Profile function execution
function profile_function
    set -l start_time (date +%s%N)
    your_function $argv
    set -l end_time (date +%s%N)
    set -l duration (math "($end_time - $start_time) / 1000000")
    echo "Function took $duration ms"
end

Fish Shell represents a paradigm shift in shell design, prioritizing user experience and discoverability over strict POSIX compliance. Its intelligent autosuggestions, syntax highlighting, and web-based configuration make it particularly appealing to newcomers to the command line, while its powerful scripting capabilities and extensive plugin ecosystem satisfy the needs of advanced users. Although its different syntax requires some adjustment for users coming from traditional shells, Fish's focus on being helpful and intuitive makes it an excellent choice for interactive shell use and modern development workflows.