June 8, 2025
Getting Started with Fish Shell Scripting: A Beginner’s Guide

Getting Started with Fish Shell Scripting: A Beginner’s Guide

In the world of command-line interfaces, users often find themselves choosing between several popular shells: Bash, Zsh, and the relatively newer Fish (Friendly Interactive Shell). While Bash and Zsh have been staples for years, Fish has gained traction for its user-friendly features and modern design. In this article, we’ll delve into Fish shell, comparing it with Bash and Zsh to help you determine which might be the best fit for your workflow.

What is Fish Shell?

Fish shell, short for “Friendly Interactive SHell,” was designed to be user-friendly and interactive. Unlike traditional shells, it emphasizes simplicity and usability without sacrificing powerful features. Fish offers a clean syntax, helpful suggestions, and a rich set of features out of the box, making it particularly appealing for both newcomers and seasoned developers.

Key Features of Fish Shell

  • Sensible Defaults: Fish comes with sensible defaults that require minimal configuration. This means that many features work seamlessly right after installation, allowing users to focus on productivity rather than setup.
  • Syntax Highlighting: As you type commands, Fish provides real-time syntax highlighting, making it easier to identify errors before executing commands.
  • Autosuggestions: Fish suggests commands as you type, based on your command history and completion options. This feature enhances efficiency and reduces typing errors.
  • User-Friendly Syntax: The syntax in Fish is designed to be intuitive. For example, you don’t need to use $ for variable expansion, making it more readable.
  • Built-in Documentation: Fish includes comprehensive built-in help documentation, allowing users to easily look up commands and functions directly within the shell.
  • Customizable: Fish supports a variety of themes and can be customized using its own scripting language, making it visually appealing and adaptable to user preferences.

Comparing Fish, Bash, and Zsh

Usability

  • Bash: Bash is widely used and highly compatible, making it a go-to for many users. However, its configuration can be cumbersome, requiring knowledge of various scripts and options.
  • Zsh: Zsh offers more features than Bash, including powerful globbing and improved tab completion. However, its advanced features can be overwhelming for new users, requiring additional configuration to unlock its full potential.
  • Fish: Fish shines in usability with its focus on user experience. The out-of-the-box features, combined with real-time feedback (like syntax highlighting and autosuggestions), make it the most accessible choice for users at any skill level.

Configuration and Customization

  • Bash: Bash relies heavily on .bashrc and .bash_profile for configuration, which can be complex for beginners. Customization often requires scripting knowledge.
  • Zsh: Zsh configurations can be extensive, especially with frameworks like Oh My Zsh, which provide plugins and themes. While powerful, it can lead to a steep learning curve.
  • Fish: Fish uses a more straightforward approach to configuration. The fish_config command launches a web-based configuration interface, simplifying customization and theme selection.

Performance

  • Bash: Bash is lightweight and performs well for most tasks, making it a solid choice for scripting.
  • Zsh: While slightly more resource-intensive than Bash, Zsh’s performance is still commendable, especially with optimized configurations.
  • Fish: Fish may consume more memory than Bash and Zsh due to its feature-rich nature. However, for many users, the trade-off in usability justifies any slight performance overhead.

Fish Shell Scripting

Variables

The set command is used to create and manage variables. It allows you to define new variables, assign values to them, and modify their attributes

basic variables

# basic variables 

set my_variable "Hello, Fish!"

#  Array Variables

set my_array 1 2 3 4 5
echo $my_array[1]

Local and global variables

set -g global_var "Accessible everywhere"

function my_function
    set -l local_var "Only here"
    echo $local_var
end

Unsetting Variables

To remove a variable, use the set command with the -e option:

set -e my_variable

Exporting and unexporting variables

set -gx VARIABLE_NAME value
set -gu VARIABLE_NAME value

Constant / Readonly variables

Fish does not have inbuilt support for readonly variables .

Print all global, exported variables:

set -gx

String concatenation

# using the set -a and -p fags

set var "gel"
set -a var "bell"  # append with space
set -p var "dell" # prepend with space
echo $var

# nomarl set 

set first_name "John"
set last_name "Doe"
set full_name "$first_name $last_name"

echo $full_name  # Outputs: John Doe

# using stirng command 

set part1 "Hello"
set part2 "World"
set result (string join " " $part1 $part2)

echo $result  # Outputs: Hello World

# using echo command
echo "The full name is: $first_name $last_name"  # Outputs: The full name is: John Doe

#  Using String Replacement or Modification
set greeting "Hello"
set name "Alice"
set message (string replace " " " " "$greeting, $name!")

echo $message  # Outputs: Hello, Alice!

Flow control

if else block

if test -f foo.txt
    echo foo.txt exists
else if test -f bar.txt
    echo bar.txt exists
else
    echo foo.txt and bar.txt do not exist
end

# with and 
if test -f foo.txt
   and test -r foo.txt
   echo "foo.txt exists and is readable"
end

# with or 

if test -f foo.txt
           or test -f moo.txt
           echo "foo.txt exists and is readable"
end

# with not 
if not test -f spoon
    echo There is no spoon
    exit 1
end

# other variations 

set age 20
set has_permission true

if test $age -ge 18 && $has_permission
    echo "Access granted."
else
    echo "Access denied."
end

set age 16
set has_permission false

if test $age -ge 18 || $has_permission
    echo "Access granted."
else
    echo "Access denied."
end

switch block

set fruit "apple"

switch $fruit
    case "apple"
        echo "You have an apple."
    case "banana"
        echo "You have a banana."
    case "orange"
        echo "You have an orange."
    case "*"
        echo "Unknown fruit."
end

Loops

for loop

set fruits apple banana cherry

for fruit in $fruits
    echo "I like $fruit"
end

# Looping Through Command Output
for file in (ls)
    echo "File: $file"
end

while loop

set count 1

while test $count -le 5
    echo "Count is $count"
    set count (math "$count + 1")
end

# single line loop
while test -f foo.txt; or test -f bar.txt ; echo file exists; sleep 10; end

untill loop

set count 1

until test $count -gt 5
    echo "Count is $count"
    set count (math "$count + 1")
end

break and continue

for i in *.tmp
    if grep smurf $i
        continue
    end
    # This "rm" is skipped over if "continue" is executed.
    rm $i
    # As is this "echo"
    echo $i
end

for i in *.c
    if grep smurf $i
        echo Smurfs are present in $i
        break
    end
end

Taking input from the user

echo "Enter your name:"
read user_name
echo "Hello, $user_name!"

# reading input into variable 
echo hello|read foo

# multi input 
echo "Enter your first name and last name:"
read first_name last_name
echo "Hello, $first_name $last_name!"

# Providing a Default Value

set default_name "Guest"
echo "Enter your name (default: $default_name):"
read -i $default_name user_name
echo "Hello, $user_name!"

# * The -i option allows you to specify a default value that appears in the input prompt.
# * If the user just hits Enter, user_name will take the value of default_name.

# Reading Passwords (Hidden Input)

echo "Enter your password:"
read -s user_password
echo "Password entered successfully."

# reading with delimiter 

echo a==b==c | read -d == -l a b c
echo $a # a
echo $b # b
echo $c # c


# * -d ==: The -d option specifies the delimiter to use for splitting the input. Here, == is used as the delimiter.
# * l a b c: The -l option tells read to assign the split values to the variables a, b, and c.

# This will remove \ and assign it to a 
 
echo 'a\ b' | read -t first second
echo $first # outputs "a b", $second is empty

Echo and printf

echo

echo 'Hello World'

# no new line 

echo -n "Hello, "
echo "world!"  # This will print on the same line

# with escape sequence 
echo -e 'Top\nBottom'

# supported escape sequence 


# \ backslash
# \a alert (BEL)
# \b backspace
# \c produce no further output
# \e escape
# \f form feed
# \n new line
# \r carriage return
# \t horizontal tab
# \v vertical tab
# \xHH byte with hexadecimal value HH (1 to 2 digits)

printf

The printf command in Fish shell is used for formatted output, similar to printf in C and other programming languages. It provides more control over the output format compared to echo

set name "Alice"
set age 30
set height 5.5

printf "Name: %s\nAge: %d\nHeight: %.1f feet\n" $name $age $height

# other examples 
printf '%s\t%s\n' flounder fish
printf '%s: %d' "Number of bananas in my pocket" 42

Input Parameters

basics


# Count the number of arguments
set arg_count (count $argv)
echo "Number of arguments: $arg_count"

# Print all arguments
echo "Arguments passed:"
for arg in $argv
    echo $arg
end

# parsing the arguments 
set args $argv
switch $args[1]
case "build"
  echo "build is passed"
case "run"
  echo "run is passed" 
case "*"
  echo "error" 
end 

coloured output

For the coloured output we will use the tput

# parsing the arguments 
set args $argv
switch $args[1]

case "build"
  tput setaf 15 ;and echo "building TECH image"
case "run"
  tput setaf 12 ;and echo "Running TECH image"
case "*"
   tput sgr0
  echo "error" 
end 

Colours supported by your terminal theme

You can create a Fish shell script to display all terminal colors using the tput command. The tput command interacts with the terminal capabilities, allowing you to change colors and other attributes.

Here’s a simple script that lists all 256 colors supported by the terminal using tput

#!/usr/bin/env fish

# Loop through all 256 colors
for i in (seq 0 255)
    # Set the color using tput
    set color (tput setaf $i)
    
    # Print the color and its number
    printf "$color%3d: This is color %d\n" $i $i
end

# Reset to default color
tput sgr0

Functions

Functions in Fish shell allow you to encapsulate reusable code into named blocks, making your scripts cleaner and more organized. Here are some examples to illustrate how to define and use functions in Fish shell.

# simple functon with one argument 
function greet
    echo "Hello, $argv!"
end

greet Alice

# Function with Default Arguments
function greet
    if test (count $argv) -eq 0
        set argv "World"
    end
    echo "Hello, $argv!"
end

greet

# Function with Conditional Logic

function check_even_odd
    set number $argv[1]
    if test (math "$number % 2") -eq 0
        echo "$number is even."
    else
        echo "$number is odd."
    end
end

check_even_odd 5

# Function with Multiple Arguments

function add_numbers
    set sum 0
    for num in $argv
        set sum (math "$sum + $num")
    end
    echo "Sum: $sum"
end

add_numbers 1 2 3 4

# Returning Values from Functions

function multiply
    set result (math "$argv[1] * $argv[2]")
    echo $result
end

set product (multiply 3 4)
echo "Product: $product"

Check whether a variable is defined

set var1 hello

if set -q var1; or set -q var2
    echo either variable defined
end

Signal handler

inbuilt handlers

#!/usr/bin/env fish

function my_signal_handler --on-signal WINCH
    echo "Got WINCH signal!"
    ecoh "this is fired when terminal window size changes"
end

function on_exit --on-event fish_exit
    echo fish is now exiting
end

while true
    sleep 1
end

# other signals 

# * fish_prompt is emitted whenever a new fish prompt is about to be displayed.
# * fish_preexec is emitted right before executing an interactive command. The commandline is passed as the first parameter. Not emitted if command is empty.
# * fish_posterror is emitted right after executing a command with syntax errors. The commandline is passed as the first parameter.
# * fish_postexec is emitted right after executing an interactive command. The commandline is passed as the first parameter. Not emitted if command is empty.
# * fish_exit is emitted right before fish exits.
#* fish_cancel is emitted when a commandline is cleared.

Using trap

#!/usr/bin/env fish

function handle_sigint
    echo "Caught Ctrl+C! Exiting gracefully..."
    exit 0
end

# Set up the trap for SIGINT
trap 'handle_sigint' SIGINT

# Simulate a long-running process
echo "Press Ctrl+C to see the message..."
while true
    sleep 1
end

Custom events triggering

function handler --on-event imdone
    echo generator is done $argv
end

function generator
    sleep 1
    # The "imdone" is the name of the event
    # the rest is the arguments to pass to the handler
    emit imdone with $argv
end

Command substitution


# exmaple 1
set current_directory (pwd)
echo "Current directory: $current_directory"

# exmaple 2

for i in (ls)
    echo $i 
    set filesize (stat -c %s $file)
    echo "File: $file, Size: $filesize bytes"
end


# example 3

set num_files (ls | wc -l)
echo "Number of files in the directory: $num_files"

# Nested Command Substitution

set longest_file (basename (ls | sort | tail -n 1)
echo "Longest file name: $longest_file"

# Using Command Substitution with Conditions

if test (whoami) = "root"
    echo "You are running as root."
else
    echo "You are not running as root."
end

String manipulation

cho "zero $(echo one\ntwo\nthree) four"
echo \"(echo one\ntwo\nthree | string collect)\"
echo \"(echo one\ntwo\nthree | string collect -N)\"


echo foo(true | string collect --allow-empty)bar
echo foo(ls | string collect --allow-empty)bar

seq 3 | string join ...
string join '' a b 
string join 't' a b 

# print string length
string length 'hello, world'

# lower case 
string lower ABC
string upper abc

string shorten foo foobar
string shorten --char="..." foo foobar
string shorten --char="" --max 4 abcdef 123456

# repeate strings
string repeat -n 2 'foo '
echo foo | string repeat -n 2
string repeat -n 2 -m 5 'foo'
string repeat -m 5 'foo'

# replace strings 
string replace is was 'blue is my favorite'
string replace 3rd last 1st 2nd 3rd

# split strings
string split . example.com
string split -r -m1 / /usr/local/bin/fish
string split '' abc

# Count files in a directory, without being confused by newlines.
count (find . -print0 | string split0)

# slicing strings 
string sub --length 2 abcde
string sub -s 2 -l 2 abcde
string sub --start=-2 abcde
string sub --end=3 abcde
string sub -e -1 abcde
string sub -s 2 -e -1 abcde
string sub -s -3 -e -2 abcde

string trim ' abc  '
string trim --right --chars=yz xyzzy zany

eval command

eval evaluates the specified parameters as a command. If more than one parameter is specified, all parameters will be joined using a space character as a separator.

set cmd ls \| cut -c 1-12
eval $cmd

Contains

if contains cat $animals
   echo Your animal list is evil!
end

for i in ~/bin /usr/local/bin
    if not contains $i $PATH
        set PATH $PATH $i
    end
end

Conclusion

Fish shell scripting offers a refreshing alternative to traditional scripting languages. Its user-friendly syntax, real-time feedback, and built-in functions make it an excellent choice for both new and experienced users. By harnessing the power of Fish, you can automate repetitive tasks efficiently and effectively.

As you explore Fish further, you’ll discover its vast capabilities for enhancing your command-line experience. Whether you’re writing simple scripts or building more complex automation, Fish can help streamline your workflow and make scripting a more enjoyable process. Happy scripting!

Leave a Reply

Your email address will not be published. Required fields are marked *