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!