fish
(Friendly Interactive SHell) is a really good command line shell which comes with tons of great features (syntax highlighting, etc) out of the box. Read the official website to get a sense of whether you would like using fish.
For me, I really like being able to write shell scripts that are actually easier to understand. Thanks to learning fish, I now find bash scripts much easier to read. The feature of the exit code being displayed at the command line is very handy. The other commandline features are also very nice.
fish is probably in your distro's repositories under the name fish-shell (on e.g. Void) or fish (on e.g. Debian)
fish is almost compatible with [ba]sh with a few important exceptions:
set myvariable hello instead of bash myvariable=hello. However the syntax EDITOR=nano sudoedit /etc/hosts syntax still works.$PATH is done through the $fish_user_paths variable. To add another directory to the path, do set -U fish_user_paths $fish_user_paths /path/to/additional/directory. The -U flag stands for "universal" and sets the variable everywhere.$status stores the exit code of the previous command (in bash, this is $?). The variable $fish_pid stores the shell's Process ID - very handy for making names for temporary files (in bash, this is $$).You also might want to change the theme. When I first installed fish, the default colour scheme included a dark blue which was really hard to see against the black background, so I switched to the "dracula" theme. To display themes, do fish_config theme show and to select one, fish_config theme choose $NAME_OF_THEME. Tab completion is your friend! Also, many people recommend highlighting comments brightly so they stand out - this doesn't really matter on the command line, more for editors.
TODO it's var[1..3] etc for sublists. you can use string split to chop strings up. you can use count to get the length of a list.
To make a permanent alias, do alias --save NAME 'command1; command2'. You can separate multiple commands with semicolons; you should put quotes around the commands you want to run.
To remove an alias, do functions --erase NAME followed by funcsave NAME.
You can use funcsave FUNCNAME, where FUNCNAME is a function you've defined, to save it; it will be available next time you start the fish shell; you can remove it the same way you remove a permanent alias. In fact, alias is simply a function that creates (and optionally saves) functions.
function maybe_quit -a s code message
# s is the status code of the command in question
# code is the status code to exit this script with
# message is the error message to display
if test $s = 0
return
end
echo $message 1>&2
# 1>&2 redirects standard output (1) to standard error (3)
exit $code
end
You can use this to display an error message and quit if a command exited with a non-zero status. An example below:
set dirname /path/to/directory
mkdir -p $dirname
maybe_quit $status 1 "could not create directory!" # e.g. permission denied
echo "it worked!"
Update! - 2026-04-09 - There is a more efficient, more fishy way of doing this:
function error -a code message
# code is the status code to exit this script with
# message is the error message to display
echo $message 1>&2
# 1>&2 redirects standard output (1) to standard error (3)
exit $code
end
Invoking:
set dirname /path/to/directory
mkdir -p $dirname
or error 1 "could not create directory!" # e.g. permission denied
echo "it worked!"
The "or" statement runs the following command if the previous command returned false (exit code not zero).
I have decided to leave my old method here as it's easier for newbies to understand how it works. As you learn a new language your usage habits will change a lot.