Fish shell configuration file

Table of Contents

This is my Fish shell configuration, as always, nothing fancy.

~/.config/fish/config.fish

From all the shells I've used, this is the most recent. And it is intentionally not fully POSIX compliant, meaning porting POSIX compliant script may require significant rewrite.

As for now, just to conditionals for when called as a login shell or when called interactively. Most people used fish as an interactive shell only, meaning the default shell (usually bash) is still used for initializing. But when I used fish I set it up as my default shell (using chsh).

to check if you're in interactive or login shell, just run the following snippet:

status --is-interactive || echo $status

and check its output

interactive settings

if status is-interactive
    # Commands to run in interactive sessions can go here
    [ -d "$HOME/.local/bin" ] && set -x PATH $HOME/.local/bin $PATH

    # completions
    for dir in /usr/share/fish/completions \
        /usr/share/fish/vendor_completions.d \
        /usr/local/share/fish/completions \
        ~/.config/fish/completions
        if [ -d "$dir" ]
            for files in "$dir/*"
                [ -f $files ] && source $files
            end
        end
    end
end

Login shell

This allow us to run fish as a login shell, while still having all the environment variables I would typically have in bash1.

# source /etc/profile with bash
if status is-login
    exec bash -c "test -e /etc/profile && source /etc/profile;\
    exec fish"
end

~/config/fish/conf.d

Mostly just ports from my POSIX shell configuration.

00-distro.fish

# ~/.config/fish/conf.d/00-distro.fish
# <alexforsale@yahoo.com>

if [ -f /etc/os-release ]
    # freedesktop.org and systemd
    set -x DISTRO (grep '^ID=' /etc/os-release| sed 's/^ID=//')
    set -x DISTROVER (grep '^VERSION_ID' /etc/os-release| sed 's/^ID=//')
else if [ (command -v lsb_release >/dev/null) ]
    # linuxbase.org
    set -x DISTRO (lsb_release -si | awk '{print tolower ($0)}')
    set -x DISTROVER (lsb_release -sr | awk '{print tolower ($0)}')
else if [ -f /etc/lsb-release ]
    # For some versions of Debian/Ubuntu without lsb_release command
    set -x DISTRO (grep '^ID' /etc/lsb-release| sed 's/^DISTRIB_ID=//')
    set -x DISTROVER (grep '^VERSION_ID' /etc/lsb-release| sed 's/^DISTRIB_RELEASE=//')
else if [ -f /etc/debian_version ]; then
    # Older Debian/Ubuntu/etc.
    set -x DISTRO Debian
    set -x DISTROVER (cat /etc/debian_version)
else
    set -x DISTRO (uname -s)
    set -x DISTROVER (uname -r)
end

00-locale.fish

# ~/.config/fish/conf.d/00-locale.fish
# <alexforsale@yahoo.com>

set -x LANG en_US.UTF-8

if [ -z $MM_CHARSET ]
  set -x MM_CHARSET $LANG
end

01-xdg_base_directory.fish

# ~/.config/fish/conf.d/01-xdg_base_directory.fish
# <alexforsale@yahoo.com>

if [ -z $XDG_CONFIG_HOME ]
    if [ -d $HOME/.config ]
        mkdir -p $HOME/.config
    end
    set -x XDG_CONFIG_HOME $HOME/.config
end

if [ -z $XDG_CACHE_HOME ]
    if [ -d $HOME/.cache ]
        mkdir -p $HOME/.cache
    end
    set -x XDG_CACHE_HOME $HOME/.cache
end

if [ -z $XDG_DATA_HOME ]
    if [ -d $HOME/.local/share ]
        mkdir -p $HOME/.local/share
    end
    set -x XDG_DATA_HOME $HOME/.local/share
end

if [ -z $XDG_DESKTOP_DIR ]
    set -x XDG_DESKTOP_DIR $HOME/Desktop
    mkdir -p $XDG_DESKTOP_DIR
end
if [ -z $XDG_DOWNLOAD_DIR ]
    set -x XDG_DOWNLOAD_DIR $HOME/Downloads
    mkdir -p $XDG_DOWNLOAD_DIR
end
if [ -z $XDG_TEMPLATES_DIR ]
    set -x XDG_TEMPLATES_DIR $HOME/Templates
    mkdir -p $XDG_TEMPLATES_DIR
end
if [ -z $XDG_PUBLICSHARE_DIR ]
    set -x XDG_PUBLICSHARE_DIR $HOME/Public
    mkdir -p $XDG_PUBLICSHARE_DIR
end
if [ -z $XDG_DOCUMENTS_DIR ]
    set -x XDG_DOCUMENTS_DIR $HOME/Documents
    mkdir -p $XDG_DOCUMENTS_DIR
end
if [ -z $XDG_MUSIC_DIR ]
    set -x XDG_MUSIC_DIR $HOME/Music
    mkdir -p $XDG_MUSIC_DIR
end
if [ -z $XDG_PICTURES_DIR ]
    set -x XDG_PICTURES_DIR $HOME/Pictures
    mkdir -p $XDG_PICTURES_DIR
end
if [ -z $XDG_VIDEOS_DIR ]
    set -x XDG_VIDEOS_DIR $HOME/Videos
    mkdir -p $XDG_VIDEOS_DIR
end

if [ -n $GUIX_LOCPATH ] || [ $DISTRO = "guix" ]
    if [ -d $HOME/.nix-profile/share ]
        set -x XDG_DATA_DIRS "$XDG_DATA_DIRS:$HOME/.nix-profile/share"
    end
end

02-editors.fish

# ~/.config/fish/conf.d/03-nix.fish
# from https://github.com/lilyball/nix-env.fish/blob/master/conf.d/nix-env.fish
# <alexforsale@yahoo.com>

if [ -f $HOME/.config/chemacs/profile ] &&
        [ -z "$CHEMACS_PROFILE" ]
    set -x CHEMACS_PROFILE (cat $HOME/.config/chemacs/profile)
else if [ -f $HOME/.emacs-profile ]
    set -x CHEMACS_PROFILE (cat $HOME/.emacs-profile)
end

if [ -n "$CHEMACS_PROFILE" ]
    set emacs_args "-s $CHEMACS_PROFILE"
end

if [ (command -v emacs) ]
    if [ ! (set -q VISUAL) ]
        set -x VISUAL "emacsclient -t $emacs_args -a ''"
    end
    if [ ! (set -q EDITOR) ]
        set -x EDITOR "emacsclient -c $emacs_args -a ''"
    end
    set -x ALTERNATE_EDITOR $VISUAL
else if [ (command -v gvim) ]
    if [ ! (set -q EDITOR) ]
        set -x EDITOR gvim
    end
    if [ ! (set -q VISUAL) ]
    end
else
    for editor in nvim leafpad l3afpad kate pluma kwrite scribe geany gedit code vi
        if [ (command -v $editor ) ]
            [ ! (set -q EDITOR) ] && set -x EDITOR $editor
        end
        set -x VISUAL $EDITOR
        set -x ALTERNATE_EDITOR $EDITOR
    end
end

03-nix.fish

# ~/.config/fish/conf.d/03-nix.fish
# from https://github.com/lilyball/nix-env.fish/blob/master/conf.d/nix-env.fish
# <alexforsale@yahoo.com>

set -l nix_profile_path /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
set -l single_user_profile_path ~/.nix-profile/etc/profile.d/nix.sh
if test -e $nix_profile_path
    # The path exists. Double-check that this is a multi-user install.
    # We can't just check for ~/.nix-profile/… because this may be a single-user install running as
    # the wrong user.

    # stat is not portable. Splitting the output of ls -nd is reliable on most platforms.
    set -l owner (string split -n ' ' (command ls -nd /nix/store 2>/dev/null))[3]
    if not test -k /nix/store -a $owner -eq 0
        # /nix/store is either not owned by root or not sticky. Assume single-user.
        set nix_profile_path $single_user_profile_path
    end
else
    # The path doesn't exist. Assume single-user
    set nix_profile_path $single_user_profile_path
end

if test -e $nix_profile_path
    # Source the nix setup script
    # We're going to run the regular Nix profile under bash and then print out a few variables
    for line in (command env -u BASH_ENV bash -c '. "$0"; for name in PATH "${!NIX_@}"; do printf "%s=%s\0" "$name" "${!name}"; done' $nix_profile_path | string split0)
        set -xg (string split -m 1 = $line)
    end

    # Insert Nix's fish share directories into fish's special variables.
    # nixpkgs-installed fish tries to set these up already if NIX_PROFILES is defined, which won't
    # be the case when sourcing $__fish_data_dir/share/config.fish normally, but might be for a
    # recursive invocation. To guard against that, we'll only insert paths that don't already exit.
    # Furthermore, for the vendor_conf.d sourcing, we'll use the pre-existing presence of a path in
    # $fish_function_path to determine whether we want to source the relevant vendor_conf.d folder.

    # To start, let's locally define NIX_PROFILES if it doesn't already exist.
    set -al NIX_PROFILES
    if test (count $NIX_PROFILES) -eq 0
        set -a NIX_PROFILES $HOME/.nix-profile
    end
    # Replicate the logic from nixpkgs version of $__fish_data_dir/__fish_build_paths.fish.
    set -l __nix_profile_paths (string split ' ' -- $NIX_PROFILES)[-1..1]
    set -l __extra_completionsdir \
        $__nix_profile_paths/etc/fish/completions \
        $__nix_profile_paths/share/fish/vendor_completions.d
    set -l __extra_functionsdir \
        $__nix_profile_paths/etc/fish/functions \
        $__nix_profile_paths/share/fish/vendor_functions.d
    set -l __extra_confdir \
        $__nix_profile_paths/etc/fish/conf.d \
        $__nix_profile_paths/share/fish/vendor_conf.d
    ### Configure fish_function_path ###
    # Remove any of our extra paths that may already exist.
    # Record the equivalent __extra_confdir path for any function path that exists.
    set -l existing_conf_paths
    for path in $__extra_functionsdir
        if set -l idx (contains --index -- $path $fish_function_path)
            set -e fish_function_path[$idx]
            set -a existing_conf_paths $__extra_confdir[(contains --index -- $path $__extra_functionsdir)]
        end
    end
    # Insert the paths before $__fish_data_dir.
    if set -l idx (contains --index -- $__fish_data_dir/functions $fish_function_path)
        # Fish has no way to simply insert into the middle of an array.
        set -l new_path $fish_function_path[1..$idx]
        set -e new_path[$idx]
        set -a new_path $__extra_functionsdir
        set fish_function_path $new_path $fish_function_path[$idx..-1]
    else
        set -a fish_function_path $__extra_functionsdir
    end

    ### Configure fish_complete_path ###
    # Remove any of our extra paths that may already exist.
    for path in $__extra_completionsdir
        if set -l idx (contains --index -- $path $fish_complete_path)
            set -e fish_complete_path[$idx]
        end
    end
    # Insert the paths before $__fish_data_dir.
    if set -l idx (contains --index -- $__fish_data_dir/completions $fish_complete_path)
        set -l new_path $fish_complete_path[1..$idx]
        set -e new_path[$idx]
        set -a new_path $__extra_completionsdir
        set fish_complete_path $new_path $fish_complete_path[$idx..-1]
    else
        set -a fish_complete_path $__extra_completionsdir
    end

    ### Source conf directories ###
    # The built-in directories were already sourced during shell initialization.
    # Any __extra_confdir that came from $__fish_data_dir/__fish_build_paths.fish was also sourced.
    # As explained above, we're using the presence of pre-existing paths in $fish_function_path as a
    # signal that the corresponding conf dir has also already been sourced.
    # In order to simulate this, we'll run through the same algorithm as found in
    # $__fish_data_dir/config.fish except we'll avoid sourcing the file if it comes from an
    # already-sourced location.
    # Caveats:
    # * Files will be sourced in a different order than we'd ideally do (because we're coming in
    #   after the fact to source them).
    # * If there are existing extra conf paths, files in them may have been sourced that should have
    #   been suppressed by paths we're inserting in front.
    # * Similarly any files in $__fish_data_dir/vendor_conf.d that should have been suppressed won't
    #   have been.
    set -l sourcelist
    for file in $__fish_config_dir/conf.d/*.fish $__fish_sysconf_dir/conf.d/*.fish
        # We know these paths were sourced already. Just record them.
        set -l basename (string replace -r '^.*/' '' -- $file)
        contains -- $basename $sourcelist
        or set -a sourcelist $basename
    end
    for root in $__extra_confdir
        for file in $root/*.fish
            set -l basename (string replace -r '^.*/' '' -- $file)
            contains -- $basename $sourcelist
            and continue
            set -a sourcelist $basename
            contains -- $root $existing_conf_paths
            and continue # this is a pre-existing path, it will have been sourced already
            [ -f $file -a -r $file ]
            and source $file
        end
    end
end

04-terminals.fish

# ~/.config/fish/conf.d/04-terminals.fish
# ${TERMINAL} apps
# <alexforsale@yahoo.com>

for term in alacritty urxvt xterm gnome-terminal terminator tilda guake konsole lxterminal yakuake st terminology xfce4-terminal
    if [ (command -v $term) ]
        [ -z $TERMINAL ] && set -x TERMINAL $term
    end
end

05-filemanagers.fish

# ~/.config/fish/conf.d/05-filemanagers.sh
# filemanager configuration
# <alexforsale@yahoo.com>

for file in ranger pcmanfm thunar caja nautilus nemo dolphin rox
    if [ (command -v $file) ]
        [ -z $FILE ] && set -e FILE $file
    end
end

06-security.fish

# ~/.config/fish/conf.d/06-security.fish
# doom emacs path
# <alexforsale@yahoo.com>

set -x GPG_TTY (tty)

# https://wiki.gentoo.org/wiki/GnuPG#Automatically_starting_the_GPG_agent
if [ -n $SSH_CONNECTION ]
    set -x PINENTRY_USER_DATA "USE_CURSES=1"
end

switch $DISTRO
    case freebsd
        for i in curses gnome3 gtk2 qt5
            if [ (command -v pinentry-$i) ]
                set _PINENTRY (command -v pinentry-$i)
            else
                set _PINENTRY (command -v pinentry)
            end
        end
    case '*'
        if [ -z "(pgrep -u $USER -x gpg-agent)" ]
            gpg-agent --pinentry-program (command -v pinentry) >/dev/null
        end
end

07-browsers.fish

# ~/.config/fish/conf.d/07-browser.fish
# ${BROWSER} configuration
# <alexforsale@yahoo.com>

# Browser
for browser in firefox google-chrome-stable midori qutebrowser chromium seamonkey falkon
    if [ (command -v browser) ]
        [ -z $BROWSER ] && set -x BROWSER $browser
    else
        for term_browser in elinks lynx w3m links
            if [ (command -v term_browser) ]
                [ -z $BROWSER ] && set -x BROWSER $term_browser
            end
        end
    end
end

08-mail_apps.fish

# ~/.config/fish/conf.d/08-mail_apps.sh
# ${MAIL_APP} configuration
# <alexforsale@yahoo.com>

for mail_app in alpine balsa evolution geary kmail kube mailspring thunderbird
    if [ (command -v $mail_app) ]
        [ -z $MAIL_APP ] && set -x MAIL_APP $mail_app
    end
end

99-doom_emacs.fish

# ~/.config/fish/conf.d/99-doom_emacs.sh
# doom emacs path
# <alexforsale@yahoo.com>

if [ (command -v emacs) ] &&
        [ -e $HOME/.emacs.d/bin/doom ]
    [ ! (command -v doom) ] &&
        set -x PATH $PATH $HOME/.emacs.d/bin
else if [ -d $HOME/.config/doom/bin ]
    [ ! (command -v doom) ]
    set -x PATH $PATH $HOME/.config/doom/bin
end

99-composer.fish

# ~/.config/fish/conf.d/99-composer.fish
# <alexforsale@yahoo.com>

if command -v composer &>/dev/null and
    test -d $HOME/.config/composer/vendor/bin
    set -x PATH $PATH $HOME/.config/composer/vendor/bin
end

99-screen.fish

# ~/.config/fish/conf.d/99-screen.fish
# configuration for GNU Screen
# <alexforsale@yahoo.com>

if [ (command -v screen) ] &&
        [ ! -d $XDG_CONFIG_HOME/screen ]
    if [ -d $HOME/.screen ]
        mv $HOME/.screen $XDG_CONFIG_HOME/screen
    else
        mkdir -p $XDG_CONFIG_HOME/screen
    end
end
set -x SCREENDIR $XDG_CONFIG_HOME/screen
chmod 700 $SCREENDIR
set -x SCREENRC $SCREENDIR/config

99-fsharp.fish

# ~/.config/fish/conf.d/99-fsharp.fish
# configuration for F#
# <alexforsale@yahoo.com>

if [ (command -v dotnet) ] &&
    [ -d $HOME/.dotnet/tools ]
    set -x PATH $PATH $HOME/.dotnet/tools
end

99-elinks.fish

# ~/.config/fish/conf.d/99-elinks.fish
# configuration for elinks
# <alexforsale@yahoo.com>


if [ (command -v elinks) ] &&
        [ -d $XDG_CONFIG_HOME/elinks ] &&
        [ -d $HOME/.elinks ]
    mv $HOME/.elinks $XDG_CONFIG_HOME/elinks
else
    mkdir -p $XDG_CONFIG_HOME/elinks
end
set -x ELINKS_CONFDIR $XDG_CONFIG_HOME/elinks

99-ccache.fish

# ~/.config/fish/conf.d/99-ccache.fish
# configuration for ccache
# <alexforsale@yahoo.com>

if [ (command -v ccache) ] &&
    [ -d /usr/lib/ccache/bin ]
    set -x PATH /usr/lib/ccache/bin $PATH
end

switch $DISTRO
    case gentoo
        [ -r /var/cache/ccache ] &&
        set -x CCACHE_DIR /var/cache/ccache
end

99-ghcup.fish

# ~/.config/fish/conf.d/99-ghcup.fish
# ghcup configuration
# <alexforsale@yahoo.com>

if [ (command -v ghcup) ]
    [ -d $HOME/.cabal/bin ] && set -x PATH $HOME/.cabal/bin $PATH
    [ -d $HOME/.ghcup/bin ] && set -x PATH $HOME/.ghcup/bin $PATH
end

99-cargo.fish

# ~/.config/fish/conf.d/99-cargo.fish
# cargo configuration
# https://wiki.archlinux.org/index.php/Rust#Cargo
# <alexforsale@yahoo.com>

if [ -d $HOME/.cargo/bin ]
    set -x PATH $HOME/.cargo/bin $PATH
end

99-ruby.fish

# ~/.config/fish/conf.d/99-ruby.fish
# ruby configuration
# <alexforsale@yahoo.com>

if [ (command -v ruby) ] &&
        [ -d (ruby -e 'print Gem.user_dir')/bin ]
    set -x PATH (ruby -e 'print Gem.user_dir')/bin $PATH
end

99-go.fish

# ~/.config/fish/conf.d/99-go.fish
# $GOPATH configuration
# <alexforsale@yahoo.com>

if [ (command -v go) ]
    # set GOPATH to ~/.local so we don't need
    # to add more PATH
    set -x GOPATH $HOME/.local
end

99-virtualenvwrapper.fish

# ~/.config/fish/conf.d/99-virtualenvwrapper.fish
# $GOPATH configuration
# <alexforsale@yahoo.com>

[ -d $HOME/.virtualenvs ] && set -x WORKON_HOME $HOME/.virtualenvs

[ -x /usr/bin/virtualenvwrapper.sh ] &&
exec bash -c "source /usr/bin/virtualenvwrapper.sh"

99-python.fish

# ~/.config/fish/conf.d/99-python.fish
# Python configuration
# <alexforsale@yahoo.com>

[ $(command -v pipenv) ] && set -x PIPENV_VENV_IN_PROJECT 1

if [ $(command -v pyenv) ]
    set -Ux PYENV_ROOT $HOME/.pyenv
    fish_add_path $PYENV_ROOT/bin
    pyenv init - | source
end

99-nano.fish

# ~/.config/fish/conf.d/99-nano.fish
# GNU Nano configuration
# <alexforsale@yahoo.com>

if [ $(command -v nano) ]
    if [ ! -d $XDG_CONFIG_HOME/nano ]
        mkdir -p $XDG_CONFIG_HOME/nano
        if [ -f $HOME/.nanorc ]
            if [ ! -f $XDG_CONFIG_HOME/nano/nanorc ]
            mv $HOME/.nanorc $XDG_CONFIG_HOME/nano/nanorc
        else
            mv $HOME/.nanorc $XDG_CONFIG_HOME/nano/nanorc.bak
        end
    end
    [ ! -d $XDG_CONFIG_HOME/nano/backups ] && mkdir -pv $XDG_CONFIG_HOME/nano/backups
    end
end

99-emacs_vterm.fish

# ~/.config/fish/conf.d/99-emacs-vterm.sh
# https://github.com/akermu/emacs-libvterm
# <alexforsale@yahoo.com>

function vterm_printf
    if begin
            [ -n "$TMUX" ]; and string match -q -r "screen|tmux" "$TERM"
        end
        # tell tmux to pass the escape sequences through
        printf "\ePtmux;\e\e]%s\007\e\\" "$argv"
    else if string match -q -- "screen*" "$TERM"
        # GNU screen (screen, screen-256color, screen-256color-bce)
        printf "\eP\e]%s\007\e\\" "$argv"
    else
        printf "\e]%s\e\\" "$argv"
    end
end

if [ "$INSIDE_EMACS" = vterm ]
    function clear
        vterm_printf "51;Evterm-clear-scrollback"
        tput clear
    end
end

function fish_title
    hostname
    echo ":"
    prompt_pwd
end

function vterm_prompt_end
    vterm_printf '51;A'(whoami)'@'(hostname)':'(pwd)
end
functions --copy fish_prompt vterm_old_fish_prompt
function fish_prompt --description 'Write out the prompt; do not replace this. Instead, put this at end of your file.'
    # Remove the trailing newline from the original prompt. This is done
    # using the string builtin from fish, but to make sure any escape codes
    # are correctly interpreted, use %b for printf.
    printf "%b" (string join "\n" (vterm_old_fish_prompt))
    vterm_prompt_end
end

function vterm_cmd --description 'Run an Emacs command among the ones been defined in vterm-eval-cmds.'
    set -l vterm_elisp ()
    for arg in $argv
        set -a vterm_elisp (printf '"%s" ' (string replace -a -r '([\\\\"])' '\\\\\\\\$1' $arg))
    end
    vterm_printf '51;E'(string join '' $vterm_elisp)
end

function find_file
    set -q argv[1]; or set argv[1] "."
    vterm_cmd find-file (realpath "$argv")
end

function say
    vterm_cmd message "%s" "$argv"
end

~/.config/fish/functions

fish_greeting

function fish_greeting
    echo "Welcome to "(fish --version)", the friendly interactive shell"
    echo Type (set_color green; echo -n help) (set_color normal; echo -n "for instructions on how to use fish")
end

This is basically the default greetings, the only change is I've added the version.

Footnotes:

Date: 2024-11-05 Tue 00:00

Author: Kristian Alexander P

Created: 2025-04-23 Wed 19:35