46

I am using fish as my shell in Debian and recently (after some upgrade) whenever I try to use command completion I have:

set: No such file or directory
set: Could not add component /usr/lib/x86_64-linux-gnu/libfm to PATH.
set: No such file or directory

Running this:

echo $PATH 

Gives me this:

/usr/lib/x86_64-linux-gnu/libfm /usr/local/bin /usr/bin /bin /usr/local/games /usr/games

In my system there is no /usr/lib/x86_64-linux-gnu/libfm, so I understand why fish is complaining, but I cannot find how to remove this path from my $PATH variable.

Does anyone know how can I do this?

suci
  • 3
tomekK
  • 561

8 Answers8

70

The 'fish' way of setting the $PATH variable is to actually use set --universal fish_user_paths $fish_user_paths /new/path/here. Then $fish_user_paths is actually prepended to the $PATH variable when a new session starts. The $PATH documentation doesn't currently tell you how to delete it though.

In fish every variable is actually a list (array), and you can conveniently access each item directly by using an index/indice. echo $fish_user_paths will print out a space delimited version of every item in the list, make the spaces newline with the translate function echo $fish_user_paths | tr " " "\n" and then put line numbers on it with the number lines function, echo $fish_user_paths | tr " " "\n" | nl. Then delete it with set --erase --universal fish_user_paths[5]. You must use --universal or it will not work in any new sessions.

If someone has the time, please submit a PR to the repo with this example. I opened an issue here.

tldr;

  1. echo $fish_user_paths | tr " " "\n" | nl // get the number of the one you want to delete, e.g. the 5th one
  2. set --erase --universal fish_user_paths[5] // erase the 5th path universally so it persists in new sessions
Elijah Lynn
  • 1,602
25

As Elijah says, best practice is to modify the fish_user_paths rather than the global PATH. To avoid ever having to Google this again…

  1. Create a couple of functions that only modify fish_user_paths
  2. Make both functions autoloading

To add to user paths:

function addpaths
    contains -- $argv $fish_user_paths
       or set -U fish_user_paths $fish_user_paths $argv
    echo "Updated PATH: $PATH"
end

To remove a user path if it exists (partial credit to this):

function removepath
    if set -l index (contains -i $argv[1] $PATH)
        set --erase --universal fish_user_paths[$index]
        echo "Updated PATH: $PATH"
    else
        echo "$argv[1] not found in PATH: $PATH"
    end
end

And of course, to make them autoloading:

funcsave addpaths; funcsave removepath

Example Usage:

> addpaths /etc /usr/libexec
Modifying PATH: /usr/local/bin /usr/bin /bin /usr/sbin /sbin
Updated PATH: /etc /usr/libexec /usr/local/bin /usr/bin /bin /usr/sbin /sbin

> removepath /usr/libexec Modifying PATH: /etc /usr/libexec /usr/local/bin /usr/bin /bin /usr/sbin /sbin Updated PATH: /etc /usr/local/bin /usr/bin /bin /usr/sbin /sbin

clozach
  • 1,173
7

This should erase paths 6 through the last path:

set -e PATH[6..-1]

The -e flag is erase. See help set.

6

Reset fish_user_paths withtout the path you don't want anymore:

 $ set -U fish_user_paths /usr/local/bin /usr/bin /bin /usr/local/games /usr/game

More info: https://fishshell.com/docs/current/tutorial.html#tut_path

gagarine
  • 1,164
3

I have made a fish function following @Elijah Lynn's answer that shows the added fish user added path entries and asks which ones you wish to remove and does so.

It's my very first fish shell script so it's probably not very good but it gets the job done. Here is the code:

function fish_remove_path --description "Shows user added PATH entries and removes the selected one"
    echo "User added PATH entries"
    set -l PATH_ENTRIES
    echo $fish_user_paths | tr " " "\n" | nl
    echo "Select the number of entry to be removed, if more than one separate the values by spaces"
    read -d " " -a PATH_ENTRIES
    for entry in $PATH_ENTRIES
        if string match -qr '^[0-9]+$' $entry
            # "$entry it is a number!"
            set -l FISH_ENTRIES (count $fish_user_paths)
            if test $entry -gt $FISH_ENTRIES
                echo "Index out of bounds, must be between 1 and $FISH_ENTRIES" 1>&2
            else
                echo "Erasing $fish_user_paths[$entry]"
                echo "Press y to continue"
                set -l confirmation
                read confirmation
                if test "$confirmation" = y
                    set --erase --universal fish_user_paths[$entry]
                else
                    echo "skipping..."
                end
            end
        else
            echo "Provided argument $entry is not a number" 1>&2
        end
    end
end

In order to use it just create a file called fish_remove_path.fish inside ~/.config/fish/functions/. Then to use it just type fish_remove_path.

Here is an example

❯ fish_add_path /home/

❯ fish_remove_path User added PATH entries 1 /home 2 /opt/homebrew/bin 3 /opt/homebrew/anaconda3/bin/ Select the number of entry to be removed, if more than one separate the values by spaces read> 1 Erasing /home Press y to continue read> y

❯ echo $fish_user_paths /opt/homebrew/bin /opt/homebrew/anaconda3/bin/

You can give it more than one entry, let's say you want to erase entries 4, 5 and 6. You just have to input them separated by space:

❯ fish_remove_path
User added PATH entries
     1  /home
     2  /opt/homebrew/bin
     3  /opt/homebrew/anaconda3/bin/
     4  /whatever1/
     5  /whatever2/
     6  /whatever3/
Select the number of entry to be removed, if more than one separate the values by spaces
read> 1 4 5 6
Erasing /home
Press y to continue
read> y
Erasing /whatever1/
Press y to continue
read> y
.....

I hope someone finds this useful, if anyone knows how to do this in a better way then please let me know.

3

Old question, I know, but dusted off by a separate new answer recently. The question, and existing answers, sound to me a bit like:

Every time I get out of bed, I cut my foot on the broken glass on floor next to the bed. What should I do?

  1. Put a bandage on your foot every time you cut your foot after getting out of bed?
  2. Or clean up the broken glass?

I hate to be so pedantic, but it seems to me that all of the existing answers (over many years) here have essentially recommended the "bandage" option.

Instead, shouldn't we focus on fixing the root issue?!

In my system there is no /usr/lib/x86_64-linux-gnu/libfm, so I understand why fish is complaining, but I cannot find how to remove this path from my $PATH variable.

In the case of this question, why is this directory being erroneously added in the first place? Where is it being added? The best way (it seems to me) to remove a directory from the $PATH variable is to prevent it from being added in the first place (clean up the broken glass) rather than attempting to fix it after-the-fact.

Finding where it was added can take a little debugging, but it shouldn't be too difficult. There are several places to look that I can think of:

System configuration

after some upgrade

The path being referenced is part of the PCMan File Manager, but I couldn't find any issues regarding its library directory being added to the path.

However, if it was a system upgrade that changed this, then start by looking at the system path:

sudo -e /etc/environment

If that's not it, then next check to see if the directory shows in the PATH when running as root:

sudo -s
# Fish should be the $SHELL which was executed, but if not ...
fish
set --show PATH

If the bad directory is still present, then it is likely a system configuration issue. If not, then it's likely in the user configuration.

For system, of course, see if you can find a reference to the bad path somewhere in /etc:

sudo grep -r libfm /etc

There's not much other than /etc/environment that should impact Fish, but it's worth a check.

There shouldn't, as far as I know, be any Systemd units that impact this. Systemd doesn't (at the time of this answer) have the ability to set the user environment without some fairly extreme gymnastics (i.e. JSON user records, which just aren't used much yet). That said, at some point it may be worth checking a sudo grep -rs libfm /usr/lib/systemd (the -s suppresses messages on binary files matching).

User-level shell configuration

If it is user-level configuration, then check your user config, of course. I'd start with:

grep -r libfm ~/.config ~/.local

If needed, expand the search:

grep -r libfm ~

You could also substitute grep -r PATH ~ in there to look for any line changing the PATH. It's possible that the libfm directory could be part of another variable that is being used to change the PATH.

Universal variable

Speaking of, there's always the chance that it is being added via $fish_user_paths. However, there's a comment from the OP in one of the deleted answers here that indicates that was not the case here.

But for other readers, if it is the case, some of the above answers might be right! To check this, however, simply:

set --show fish_user_paths

If the bad directory is actually in there, then yes, simply remove it based on the index (best example in @ElijahLynn's answer). For instance, if $fish_user_paths[3] is the erroneous path, then:

set --erase --universal fish_user_paths[3]

In the even smaller chance that it is some other variable being set and used in a PATH modification:

set --show | grep libfm

Parent process environment

Finally, there's the chance that the parent process of your shell is modifying the environment before starting Fish. This would be rare for this type of issue, but it can certainly happen. pstree and look for any common parent process of your shell. Typically, this will be login, init (WSL systems), systemd itself, or something (not sure what it would be on macOS).

Use ps -o ppid -p $fish_pid to get the parent process of your shell, then:

sudo -s
cat /proc/<pid>/environ

Is the erroneous path there? If so, then you'll need to determine why it is being set in that process (or one of its parents) to remove it.

NotTheDr01ds
  • 28,025
2

E.g. your echo $fish_user_paths returns something like

/opt/homebrew/bin /foo/bar /aaa/bbb

And you want to remove /foo/bar. Here is how:

if set -l ind (contains -i -- /foo/bar $fish_user_paths)
    set -e fish_user_paths[$ind]
end

This solution is by the Fish main developer, Fabian Boehm aka foho. A direct link to his comment on GitHub: https://github.com/fish-shell/fish-shell/issues/2639#issuecomment-451260584

The solution is easy, but it is a weird thing it is not mentioned in documentation.

jsx97
  • 219
1

As of fish v3.2.0, the idiomatic way to add to your $PATH is to use the fish_add_path function. This function by default updates your $fish_user_paths variable instead of $PATH directly. There is no built-in fish_remove_path function, but you can remove it by erasing the path via its list index depending on whether you added it to $fish_user_paths or $PATH.

set -l index (contains -i -- /path/to/remove $fish_user_paths)
and set -e fish_user_paths[$index]

set -l index (contains -i -- /path/to/remove $PATH) and set -e PATH[$index]

Here's a custom function named fish_remove_path that does the above with added bells and whistles.

Dennis
  • 472