FISH-FAQ(1)
π FISH-FAQ(1) β fish-shell, bestie edition π
okay so this is the fish-shell FAQ, and I'm gonna walk you through it like we're in the group chat. No cap, every fact stays. πβ¨
β¨ WHAT IS THE EQUIVALENT TO THIS THING FROM BASH (OR OTHER SHELLS)? β¨
See fish for bash users <>. π that's the whole vibe check.
πΈ HOW DO I SET OR CLEAR AN ENVIRONMENT VARIABLE? πΈ
Use the set <> command, bestie: β¨
set -x key value # typically set -gx key value
set -e key
Since fish 3.1 you can set an environment variable for one command using the key=value some command syntax, like in other shells. The two lines below behave identically β unlike other shells, fish will output value both times:
key=value echo $key
begin; set -lx key value; echo $key; end
Note that "exported" is not a scope <#variables-scope>, but an additional bit of state. A variable can be global and exported or local and exported or even universal and exported. Typically it makes sense to make an exported variable global. π«Ά
π¦ HOW DO I CHECK WHETHER A VARIABLE IS DEFINED? π¦
Use set -q var. For example, if set -q var; echo variable defined; end. To check multiple variables you can combine with and and or like so:
if set -q var1; or set -q var2
echo either variable defined
end
Keep in mind that a defined variable could also be empty, either by having no elements (if set like set var) or only empty elements (if set like set var ""). Read on for how to deal with those. π
π HOW DO I CHECK WHETHER A VARIABLE IS NOT EMPTY? π
Use string length -q -- $var. For example, if string length -q -- $var; echo not empty; end. Note that string length will interpret a list of multiple variables as a disjunction (meaning any/or):
if string length -q -- $var1 $var2 $var3
echo at least one of these variables is not empty
end
Alternatively, use test -n "$var", but remember that the variable must be double-quoted. For example, if test -n "$var"; echo not empty; end. The test command provides its own and (-a) and or (-o):
if test -n "$var1" -o -n "$var2" -o -n "$var3"
echo at least one of these variables is not empty
end
If you want to know if a variable has no elements, use set -q var[1]. β¨
π WHY DOESN'T SET -UX (EXPORTED UNIVERSAL VARIABLES) SEEM TO WORK? π
A global variable of the same name already exists. π
Environment variables such as EDITOR or TZ can be set universally using set -Ux. However, if there is an environment variable already set before fish starts (such as by login scripts or system administrators), it is imported into fish as a global variable. The variable scopes <#variables-scope> are searched from the "inside out", which means that local variables are checked first, followed by global variables, and finally universal variables.
This means that the global value takes precedence over the universal value. π
To avoid this problem, consider changing the setting which fish inherits. If this is not possible, add a statement to your configuration file <#configuration> (usually ~/.config/fish/config.fish):
set -gx EDITOR vim
π HOW DO I RUN A COMMAND EVERY LOGIN? WHAT'S FISH'S EQUIVALENT TO .BASHRC OR .PROFILE? π
Edit the file ~/.config/fish/config.fish [1], creating it if it does not exist (Note the leading period). π«Ά
Unlike .bashrc and .profile, this file is always read, even in non-interactive or login shells. No cap.
To do something only in interactive shells, check status is-interactive like:
if status is-interactive
# use the coolbeans theme
fish_config theme choose coolbeans
end
[1] The "~/.config" part of this can be set via $XDG_CONFIG_HOME, that's just the default. π
π HOW DO I SET MY PROMPT? π
The prompt is the output of the fish_prompt function. Put it in ~/.config/fish/functions/fish_prompt.fish. For example, a simple prompt is:
function fish_prompt
set_color $fish_color_cwd
echo -n (prompt_pwd)
set_color --reset
echo -n ' > '
end
You can also use the Web configuration tool, fish_config <>, to preview and choose from a gallery of sample prompts. Obsessed. π
Or you can use fish_config from the commandline:
> fish_config prompt show
# displays all the prompts fish ships with
> fish_config prompt choose disco
# loads the disco prompt in the current shell
> fish_config prompt save
# makes the change permanent
If you want to modify your existing prompt, you can use funced <> and funcsave <> like:
>_ funced fish_prompt
# This opens up your editor (set in $EDITOR).
# Modify the function,
# save the file and repeat to your liking.
# Once you are happy with it:
>_ funcsave fish_prompt
This also applies to fish_right_prompt <> and fish_mode_prompt <>. π
π WHY DOES MY PROMPT SHOW A [I]? π
That's the fish_mode_prompt <>. It is displayed by default when you've activated vi mode using fish_vi_key_bindings. π
If you haven't activated vi mode on purpose, you might have installed a third-party theme or plugin that does it. π
If you want to change or disable this display, modify the fish_mode_prompt function, for instance via funced <>. β¨
πΈ HOW DO I CUSTOMIZE MY SYNTAX HIGHLIGHTING COLORS? πΈ
Use the web configuration tool started by fish_config <>, or alter the fish_color family of environment variables <#variables-color>, or use the fish_config theme <> subcommand, like:
> fish_config theme show
# to demonstrate all the colorschemes
> fish_config theme choose coolbeans
# to load the "coolbeans" theme
slay. π
π HOW DO I CHANGE THE GREETING MESSAGE? π
Change the value of the variable fish_greeting or create a fish_greeting <> function. For example, to remove the greeting use:
set -U fish_greeting
Or if you prefer not to use a universal variable, use:
set -g fish_greeting
in config.fish <#configuration>. π
π¦ HOW DO I RUN A COMMAND FROM HISTORY? π¦
Type some part of the command, and then hit the up (β) or down (β) arrow keys to navigate through history matches, or press ctrl-r to open the history in a searchable pager. In this pager you can press ctrl-r or ctrl-s to move to older or younger history respectively. π°οΈ
Additional default key bindings include ctrl-p (up) and ctrl-n (down). See Searchable command history <#history-search> for more information. π«Ά
π WHY DOESN'T HISTORY SUBSTITUTION ("!$" ETC.) WORK? π
Because history substitution is an awkward interface that was invented before interactive line editing was even possible. Instead of adding this pseudo-syntax, fish opts for nice history searching and recall features. Switching requires a small change of habits: if you want to modify an old line/word, first recall it, then edit. β¨
As a special case, most of the time history substitution is used as sudo !!. In that case press alt-s, and it will recall your last commandline with sudo prefixed (or toggle a sudo prefix on the current commandline if there is anything). π
In general, fish's history recall works like this:
- Like other shells, the Up arrow,
uprecalls whole lines, starting from the last executed line. So instead of typing!!, you would hit the up-arrow. - If the line you want is far back in the history, type any part of the line and then press Up one or more times. This will filter the recalled lines to ones that include this text, and you will get to the line you want much faster. This replaces
"!vi","!?bar.c"and the like. If you want to see more context, you can pressctrl-rto open the history in the pager. alt-uprecalls individual arguments, starting from the last argument in the last executed line. This can be used instead of"!$".
See documentation <#editor> for more details about line editing in fish. π
That being said, you can use Abbreviations <#abbreviations> to implement history substitution. Here's !! only:
function last_history_item; echo $history[1]; end
abbr -a !! --position anywhere --function last_history_item
Run this and !! will be replaced with the last history entry, anywhere on the commandline. Put it into config.fish <#configuration> to keep it. π
π HOW DO I RUN A SUBCOMMAND? THE BACKTICK DOESN'T WORK! π
fish uses parentheses for subcommands. For example:
for i in (ls)
echo $i
end
It also supports the familiar $() syntax, even in quotes. Backticks are not supported because they are discouraged even in POSIX shells. They nest poorly and are hard to tell from single quotes (''). π
π§ MY COMMAND (PKG-CONFIG) GIVES ITS OUTPUT AS A SINGLE LONG STRING? π§
Unlike other shells, fish splits command substitutions only on newlines, not spaces or tabs or the characters in $IFS. π
That means if you run
count (printf '%s ' a b c)
It will print 1, because the "a b c " is used in one piece. But if you do
count (printf '%s\n' a b c)
it will print 3, because it gave count the arguments "a", "b" and "c" separately. π€―
In the overwhelming majority of cases, splitting on spaces is unwanted, so this is an improvement. This is why you hear about problems with filenames with spaces, after all. π
However sometimes, especially with pkg-config and related tools, splitting on spaces is needed. π§
In these cases use string split -n " " like:
g++ example_01.cpp (pkg-config --cflags --libs gtk+-2.0 | string split -n " ")
The -n is so empty elements are removed like POSIX shells would do. β¨
π HOW DO I GET THE EXIT STATUS OF A COMMAND? π
Use the $status variable. This replaces the $? variable used in other shells. π«Ά
somecommand
if test $status -eq 7
echo "That's my lucky number!"
end
If you are only interested in success or failure, you can run the command directly as the if-condition:
if somecommand
echo "Command succeeded"
else
echo "Command failed"
end
Or if you just want to do one command in case the first succeeded or failed, use and or or:
somecommand
or someothercommand
See the Conditions <#syntax-conditional> and the documentation for test <> and if <> for more information. π
π₯ MY COMMAND PRINTS NO MATCHES FOR WILDCARD BUT WORKS IN BASH
okay so short version bestie: quote it or escape it π
scp user@ip:/dir/"string-*"
Like, when fish sees a * that isn't quoted, it does wildcard expansion β it's literally trying to match filenames to whatever string you typed. And if the wildcard matches nothing, fish throws an error at you instead of running the command at all:
> echo *this*does*not*exist
fish: No matches for wildcard '*this*does*not*exist'. See `help expand`.
echo *this*does*not*exist
^
Now bash also tries to match files here, but when it finds nothing it just passes the literal wildcard string along instead. Which is why commands like
scp user@ip:/dir/string-*
or
apt install postgres-*
seem to work β because most of the time the string doesn't match anything, so bash hands over the plain string-* and the receiving program does its own thing with it. β¨
But it also means those same commands can just randomly stop working at any moment, the second a matching file exists (because someone created it, or because you're running from a different working directory). And to deal with that, bash needs workarounds like this:
for f in ./*.mpg; do
# We need to test if the file really exists because
# the wildcard might have failed to match.
test -f "$f" || continue
mympgviewer "$f"
done
(from <http://mywiki.wooledge.org/BashFAQ/004>)
And that's exactly why fish doesn't do it π«Ά fish expects you to quote or escape your asterisks if they're not meant to be expanded. Same energy as bash's failglob option, iykyk.
π WHY WON'T SSH/SCP/RSYNC CONNECT PROPERLY WHEN FISH IS MY LOGIN SHELL?
So this one shows up as the most cursed messages ever, like "Received message too long", "open terminal failed: not a terminal", "Bad packet length", or "Connection refused" with the weirdest output in ssh_exchange_identification messages in the debug log. π
The usual reason? fish reads your config file (~/.config/fish/config.fish) always β interactive, login, non-interactive, non-login, doesn't matter, it's reading it.
Which is honestly so much simpler, but it also means that if config.fish generates output, it'll do that even in non-interactive shells β like the ones ssh/scp/rsync start up when they connect. And those do not appreciate a chatty config file.
So anything in config.fish that produces output should be guarded with status is-interactive (or status is-login if that's more your vibe):
if status is-interactive
...
end
Same rules apply when you, say, start tmux in config.fish without guards β you'll get a message like sessions should be nested with care, unset $TMUX to force. π
π I'M GETTING WEIRD GRAPHICAL GLITCHES (A STAIRCASE EFFECT, GHOST CHARACTERS, CURSOR IN THE WRONG POSITION,...)?
So in a terminal, the app running inside it and the terminal itself have to agree on how wide characters are, otherwise cursor movement goes totally off the rails.
And this matters extra for fish, because features like syntax highlighting and autosuggestions are literally implemented by moving the cursor around. So if the width math is off, it's giving staircase effect realness. π
Sometimes there's just a disagreement on width. The causes and fixes, bestie:
- It's possible the character is too new for your system to know about β in which case you need to refrain from using it.
- fish or your terminal might not know about the character, or might handle it wrong β in which case fish or your terminal needs to be fixed, or you need to update to a fixed version.
- The character has an "ambiguous" width, and fish thinks that means a width of X while your terminal thinks it's Y. Then you either need to change your terminal's configuration or set
$fish_ambiguous_widthto the correct value. - The character is an emoji and your system only supports Unicode 8. In that case, set
$fish_emoji_widthto1.
This also means a couple of things are just straight-up unsupportable:
- Non-monospace fonts β there's no way for fish to figure out what width a specific character has, since it has no influence on the terminal's font rendering.
- Different widths for multiple ambiguous width characters β there's no way for fish to know which width you assign to each character.
π FISH DOES NOT WORK IN A SPECIFIC TERMINAL
The terminal might not meet all of fish's requirements. Please report this to your terminal's issue tracker and to fish's issue tracker β both, okay? π«Ά
π UNINSTALLING FISH
If you want to uninstall fish, first make sure fish isn't set as your shell. If you're not sure, run chsh -s /bin/bash.
If you installed it with a package manager, just use that package manager's uninstall function. If you built fish yourself β assuming you installed it to /usr/local β do this:
rm -Rf /usr/local/etc/fish /usr/local/share/fish ~/.config/fish
rm /usr/local/share/man/man1/fish*.1
cd /usr/local/bin
rm -f fish fish_indent
πΈ Author
fish-shell developers
πΈ Copyright
fish-shell developers
and that's the whole page bestie, 10/10 no notes π this was FISH-FAQ(1), version 4.9, dated Sep 08, 2026 β go forth and quote your wildcards, love u, bye π¦β¨π
π¦ where this page comes from
- arch rolling: fish 4.9.3-1 Β· GPL-2.0-only AND BSD-3-Clause AND ISC AND MIT AND PSF-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms