Search code examples
pythonshellsubprocessfish

How to add a directory to Fish shell PATH from with a Python script?


From within a Python-based installation script running on Fish shell and supporting Python 2.7 and 3.4+, I want to add a directory to the PATH environment variable if not already present. When the script exits, PATH should contain the new directory, both for the current Fish shell session as well as future logins.

To achieve this, I can think of two potential approaches:

  1. Create a localpath.fish file in ~/.config/fish/conf.d/ containing: set PATH ~/.local/bin $PATH
  2. Use Python’s subprocess to run: set -U fish_user_paths ~/.local/bin $fish_user_paths

I would prefer the latter approach, but I have tried invoking Fish via subprocess several different ways, all to no avail. For example:

>>> subprocess.check_output(["fish", "-c", "echo", "Hello", "World!"], shell=True)

On Fish 3.0.2 and Python 3.7.4, the above yields:

<W> fish: Current terminal parameters have rows and/or columns set to zero.
<W> fish: The stty command can be used to correct this (e.g., stty rows 80 columns 24).

… and the Python REPL prompt does not re-appear. Tapping CTRL-C does not exit properly, leaving the Python REPL in a mostly unusable state until quit and relaunched. (This appears to be a known issue.)

Similarly, using subprocess.run() with Python 3.7’s capture_output parameter fails to yield the expected output:

>>> subprocess.run(["fish", "-c", "echo", "Hello", "World!"], capture_output=True)
CompletedProcess(args=['fish', '-c', 'echo', 'Hello', 'World!'], returncode=0, stdout=b'\n', stderr=b'')

My questions:

  1. Is there a better strategy than either of the two methods above?
  2. If using the first approach, how would I adjust PATH for the current Fish shell session from within a Python script?
  3. If using the set -U fish_user_paths […] approach, what would be the appropriate way to invoke that from within a Python script?

Solution

  • You're on the right track, but the whole command has to be as a single argument. Also you don't want shell expansion beforehand.

    So you could write:

    subprocess.check_output(["fish", "-c", "echo hello world"])
    

    and it will do what you expect. For the PATH case:

    subprocess.check_output(["fish", "-c", "set -U fish_user_paths ~/.local/bin $fish_user_paths"])
    

    and this will modify $PATH in the parent process.