fish-shell cookbook
This document is a living book of recipes to solve particular programming problems using fish-shell. Whether you are in the mood for mackerel or salmon on the grill, there is always a distinctive and delicious way to prepare any type of fish.
Licensed CC BY-NC-SA 4.0
- Introduction
- Setup
- Getting Started
- Configuration
- Variables
- Functions
- How to create a function in fish?
- How to create a private function in fish?
- Should function names and file names match?
- Can I define more than one function in a file?
- How to show the definition of a function in fish?
- What's the difference between functions, builtins and commands in fish?
- How do I list the functions defined in fish?
- How to check if a function exists in fish?
- Arguments
- Aliases
- IO
- Concurrency
Well-known shells are bash, ash, csh, ksh and the popular zsh. All these shells are POSIX, so well-written POSIX-compliant scripts should run without modification in any of them. That's about the only good reason to learn POSIX shell.
fish is not a POSIX shell. Your bash scripts will not run in fish without some modification.
make && make install
will error with: "Unsupported use of '&&'. In fish, please use 'COMMAND; and COMMAND'."
That's easy to fix.
make; and make install
Here is a quote from the fish design document:
Fish should be user friendly, but not at the expense of expressiveness. Most tradeoffs between power and ease of use can be avoided with careful design.
You can find directions in the official website or follow the directions provided here for your OS.
macOS with homebrew
brew update && brew install fish
Debian
wget http://download.opensuse.org/repositories/shells:fish:release:2/Debian_8.0/Release.key
apt-key add - < Release.key
echo 'deb http://download.opensuse.org/repositories/shells:/fish:/release:/2/Debian_8.0/ /' >> /etc/apt/sources.list.d/fish.list
apt-get update
apt-get install fish
Ubuntu
sudo apt-add-repository ppa:fish-shell/release-2
sudo apt-get update
sudo apt-get install fish
CentOS
cd /etc/yum.repos.d/
wget http://download.opensuse.org/repositories/shells:fish:release:2/CentOS_7/shells:fish:release:2.repo
yum install fish
Fedora
cd /etc/yum.repos.d/
wget http://download.opensuse.org/repositories/shells:fish:release:2/Fedora_23/shells:fish:release:2.repo
yum install fish
Arch Linux
pacman -S fish
Gentoo
emerge fish
From source
sudo apt-get -y install git gettext automake autoconf ncurses-dev build-essential libncurses5-dev
git clone -q --depth 1 https://github.com/fish-shell/fish-shell
cd fish-shell
autoreconf && ./configure
make && sudo make install
Once you have installed fish and it's somewhere in your $PATH
, e.g. /usr/local/bin, you can make it your default login shell.
echo /usr/local/bin/fish | sudo tee -a /etc/shells
chsh -s /usr/local/bin/fish
Use which
.
which fish
/usr/local/bin/fish
The best way to learn fish is to read the official documentation and tutorial.
The prompt is where you type commands and interact with your shell interpreter, e.g, fish. Read more about the UNIX prompt here.
Maybe it looks like this:
x@mbp ~/C/fish-shell>
The tilde ~
is an abbreviation of the home directory, for example /users/x/home, /Users/x, etc.
The @
means at. I can see x
, my user, is logged into mbp
, which is the name I gave to my workstation.
The forward slash /
is the path delimiter. At a glance, I can see the current directory is in the vicinity of ~
, somewhere inside C/fish-shell. The C is the first letter of the parent directory, Code in my case.
As of fish >=2.3, you can customize the length of the abbreviated path.
set fish_prompt_pwd_dir_length NUMBER
or
set fish_prompt_pwd_dir_length 0
for no abbreviations.
x@mbp ~/Code/fish-shell
The greater than symbol >
is used here to indicate the end of the prompt.
You don't like these conventions? Create your own prompt the way you like it.
See How to create my own prompt in fish?
You can find out where you are via the read-only environment variable $PWD
.
echo $PWD
/Users/x/Code/fish-shell
Another way to find out the current directory is via the pwd
builtin.
pwd
/Users/x/Code/fish-shell
In fish, both $PWD
and pwd
always resolve symbolic links. This means that, if you are inside a directory that is a symbolic reference to another, you always get the path to the real directory.
Interactively, pwd
is easier to type. For scripting, $PWD
is a function call less expensive.
Example
set -l cwd (pwd)
echo "The current working directory is: $cwd"
# Versus
echo "The current working directory is: $PWD"
To run a command type the name of the command and press return.
ls
Or, start typing the command you are looking for, and press tab. fish will use the builtin pager which you can browse and select the command interactively.
fish knows what commands are available by looking at the $PATH
environment variable. This variable contains a list of paths, and every binary file inside any of those paths can be run by their name.
Print your $PATH
contents.
printf "%s\n" $PATH
/usr/local/bin
/usr/bin
/bin
or list every command in your system and display them in columns.
ls $PATH | column
If the list is truncated, use:
ls $PATH | column | less
Use k
and j
to navigate the list down / up, and q
to exit.
The $PATH
variable is created at the start of the fish process during the environment initialization. You can modify, prepend or append to this variable yourself, e.g, in ~/.config/fish/config.fish.
Similar to the type
, builtin
and functions
builtins previously introduced, *nix systems often include one or more shell-agnostic alternatives, e.g, which
, apropos
, whatis
, etc.
These commands overlap in functionality, but also possess unique features. Consult your system's manpage for details.
Every command returns an exit code to indicate whether they succeeded or not. An exit code of 0 means success. Anything else means failure. Different commands use different integers to represent the different errors that can happen.
You can check the exit code of any command using the special, read-only variable $status
.
my_command
echo $status
Use the set
builtin.
set foo 42
The set
builtin accepts the following flags to explicitly declare the scope of the variable:
-l
,--local
: available only to the innermost block-g
,--global
: available outside blocks and by other functions-U
,--universal
: shared between all fish sessions and persisted across restarts of the shell-x
,--export
: available to any child process spawned in the current session
If no scope modifier is used, the variable will be local to the current function, otherwise it will be global.
If the variable has already been defined, the previous scope will be used.
Local Variables
The variable foo
will not be available outside the if
block.
if true
set -l foo 42
end
echo "foo=$foo" # foo=
Global Variables
The variable foo
will be avaiable outside the if
block.
if true
set -g foo 42
end
echo "foo=$foo" # foo=42
Universal Variables
The variable foo
will be preserved and available to future shell sessions.
set -U foo 42
fish
echo "foo=$foo" # foo=42
Exported Variables
The variable foo
will be local and exported, therefore available to the fish
child process created inside the if
block.
if true
set -lx foo 42
fish -c 'echo "foo=$foo"' # foo=42
end
The variable foo
will be global, but since it's not exported, it won't be available to the fish
child process.
set -g foo 42
fish -c 'echo "foo=$foo"' # foo=
The variable GPG_AGENT_INFO
will be universal and exported, therefore preserved across future shell sessions and child processes.
set -Ux GPG_AGENT_INFO /Users/x/.gnupg/S.gpg-agent:12345:2
Use the set
builtin and the scope modifier -x
or --export
.
set -x foo 42
fish -c 'echo "foo=$foo"' # foo=42
Use the set
builtin without any modifier flags.
set
To print only the variable names, without the values, use --name
.
set --names
To not truncate long lines use --long
.
set --long
The correct way to persistently add a path to your $PATH
is using fish $fish_user_paths
variable.
set -U fish_user_paths $fish_user_paths my_path
See $PATH
in the fish tutorial for more information.
Use the set
builtin with the -e
or --erase
flag in combination with the contains
builtin to find the index of the path you want to remove.
if set -l index (contains -i $my_path $PATH)
set -e PATH[$index]
end
Use the contains
builtin.
if contains $my_path $PATH
# $my_path is in $PATH
end
Use the function
builtin.
function mkdirp
mkdir -p $argv
end
To make this function available in future fish sessions save it to ~/.config/fish/functions/mkdirp.fish. A clean way to accomplish this is using the funcsave
function.
funcsave mkdirp
Alternative, you can use the functions
builtin to write the function definition to a file.
functions mkdirp > ~/.config/fish/functions/mkdirp.fish
You can't. In fish, functions are always public.
As a workaround, use a custom namespace to prefix any function you want to treat as private.
function _prefix_my_function
end
It's not impossible to simulate private scope using functions -e
, however it's likely to perform poorly.
Example
function foo
function _foo
echo Foo
functions -e _foo # Erase _foo
end
_foo
end
Yes. The lazy-loading / autoloading mechanism relies on this convention to work.
If you have a file ~/.config/fish/functions/foo.fish with a valid function definition bar
:
- In a new shell, trying to run
bar
produces an unknown-command error. - Typing
foo
will highlight as a valid command, but produce an unknown-command error. - Trying to run
bar
again now works as intended.
Example
Save bar
to ~/.config/fish/functions/foo.fish.
function bar
echo Bar
end
functions bar > ~/.config/fish/functions/foo.fish
Create a new shell session.
fish
Try to run bar, then foo, then bar again.
bar
# fish: Unknown command 'bar'
foo
# fish: Unknown command 'foo'
bar
# Bar
Yes, you can. Note that fish does not have private functions, so every function in the file ends up in the global scope when the file is loaded. Also, every function is eagerly loaded as well, which it's not as effective as using one function per file.
If you know the command is a function, use the functions
builtin.
functions my_function
If you are not sure whether the command is a function, a builtin or a system command, use type
.
type fish
fish is /usr/local/bin/fish
System commands are executable scripts, binaries or symbolic links to binaries present in your $PATH
variable. A command runs as a child process and has only access to environment variables which have been exported. Example: fish
.
Functions are used-defined. Some functions are included with your fish distribution. Example: eval
.
Builtins are commands compiled with the fish executable. Builtins have access to the environment, so they behave like functions. Builtins do not spawn a child process. Example: functions
.
Use the functions
builtin without arguments.
The list will omit functions whose name start with an underscore. Functions that start with an underscore are often called hidden. To show everything, use functions -a
or functions --all
.
Alternatively, launch the fish Web-based configuration and navigate to the /functions tab.
fish_config functions
Use the type
function to query information about commands, builtins or functions.
if not type --quiet "$command_name"
exit 1
end
Use builtin --names
to query builtins.
if not contains -- "$command_name" (builtin --names)
exit 1
end
Use functions --query
to check if a function exists.
if not functions --query "$command_name"
exit 1
end
Use command --search
for other commands.
if not command --search "$command_name" > /dev/null
exit 1
end
Easier in fish >= 2.5
if not command --search --quiet "$command_name"
exit 1
end
Use the $argv
variable.
function Foo
printf "%s\n" $argv
end
Foo foo bar baz
foo
bar
baz
Use the $argv
variable. Pass the arguments when running the script.
fish ./my_script foo bar baz
foo
bar
baz
Example: my_script
#!/usr/bin/env fish
printf "%s\n" $argv
Use a for
loop.
for option in $argv
switch "$option"
case -f --foo
case -b --bar
case \*
printf "error: Unknown option %s\n" $option
end
end
For a more complete CLI parsing solution, see getopts
.
Create a function
and save it to ~/.config/fish/functions.
function rimraf
rm -rf $argv
end
For backwards compatibility with POSIX shells, use the alias
function.
alias rimraf "rm -rf"
Avoid using alias
inside ~/.config/fish/config.fish. See the next section.
Aliases created with alias
will not be available in new shell sessions. If that's the behavior you need, then alias
is acceptable for interactive use.
To persist aliases across shell sessions, create a function
and save it to ~/.config/fish/functions. This takes advantage of fish function lazy-loading / autoloading mechanism.
Using alias
inside ~/.config/fish/config.fish will slow down your shell start as each alias/function will be eagerly loaded.
Your fish configuration is saved to ~/.config/fish/config.fish.
To read a file line by line use the read
builtin.
while read -la line
echo $line
end < $my_file
Use the read
builtin.
read --prompt "echo 'Name: ' " -l name
Name: Marvin
echo $name
Marvin
To read from an arbitrary input stream use read
together with the while
builtin.
while read -la line
echo $line
end
Redirect stderr to $my_file
.
my_command ^ $my_file
or
my_command 2> $my_file
Redirect stdout to $my_file
.
my_command > $my_file
Redirect stdout to stderr.
my_command >&2
Redirect stderr to stdout.
my_command 2>&1
Use &
.
sleep 10 &
See also Background jobs in the fish documentation.
Use the jobs
builtin.
if jobs > /dev/null
echo Busy
end
fish has no wait
command, but you can write your own.
First, to check if there are tasks running in the background, parse the output from the jobs
builtin.
Parse by Job ID
function get_jobs
jobs $argv | command awk -v FS=\t '
/[0-9]+\t/{
jobs[++nJobs] = $1
}
END {
for (i in jobs) {
print(jobs[i])
}
exit nJobs == 0
}
'
end
Parse by Group ID
function get_jobs
jobs -g | command awk 'NR > 0 { print; i++ } END { exit i == 0 }'
end
Then, block the foreground until, all background jobs are finished.
function wait
while true
set -l has_jobs
set -l all_jobs (get_jobs)
or break
for j in $argv
if contains -- $j $all_jobs
set -e has_jobs
break
end
end
if set -q has_jobs
break
end
end
end
Example
set -l urls "https://"{google,twitter,youtube,facebook,github}".com"
for url in $urls
fish -c "curl -Lw \"$url: %{time_total}s\n\" -o /dev/null -s $url" &
end
wait (get_jobs)
fish has no wait
builtin. To wait for a background process to finish, use the solution described in How to synchronize two or more background tasks in fish?.