Skip to content(if available)orjump to list(if available)

How to add a directory to your PATH

How to add a directory to your PATH

50 comments

·February 24, 2025

ljm

The author's last post was about terminal frustrations[0]

This is easily one of the most annoying ones, especially when shells will have several different locations to read config from depending on how you start the shell, or what a desktop app will do to try and pull the config in (`exec-path-from-shell` in emacs, for example).

And you can't really, say, put it in both your `.zprofile` and `.zshrc` or `.bash_profile`, `.profile`, and `.bashrc`, because then it'll get executed more than once and you'd need to maintain some kind of state to prevent that.

[0]https://jvns.ca/blog/2025/02/05/some-terminal-frustrations/

throw0101d

> And you can't really, say, put it in both your `.zprofile` and `.zshrc` or `.bash_profile`, `.profile`, and `.bashrc`, because then it'll get executed more than once and you'd need to maintain some kind of state to prevent that.

FWIW, profiles are loaded on 'from scratch' logins; RC files are loaded all the time.

* https://superuser.com/questions/657848/why-do-we-have-login-...

* https://askubuntu.com/questions/879364/differentiate-interac...

EndShell

Somewhat related there are other confusing things around how environment variables are set (especially on Linux). There are environment variables I set for QHD and UHD monitors e.g. QT_SCALE_FACTOR

* If you are using Xorg, you put environment variables in ~/.xprofile

* If you are using Xorg on Debian, you put the environment variables in ~/.xsessionrc

* If you are using Wayland, you make a file in ~/.config/environment.d/ and set you configuration variables there.

p_ing

At what point do you consider it an "environment variable" vs "application setting" (even when called an env var by the app)? Windows has it's explicit env vars and GUI or CLI methods to set them, but if I was changing something about Explorer or some other graphical portion of the OS, I'd be going to the registry, for example. Setting the display resolution or scaling factor would be another registry entry, though I'd be using a purpose-built GUI for that.

EndShell

Well with QT_SCALE_FACTOR is goes and tells all the QT to behave in a particular way. It isn't a per application setting.

There is a similar environment variable for Steam on Linux and I would argue that it should be an application setting but for whatever reason it isn't.

Both of these are hacks around how Xorg (doesn't) handle fractional scaling.

zokier

> If you are using Wayland, you make a file in ~/.config/environment.d/ and set you configuration variables there.

This is not in any way Wayland specific, and arguably should be the default choice for setting env vars, as it should apply to all user sessions regardless of what type they are or what shell is used.

EndShell

I didn’t know that. I have to check if it works with Xorg + Gnome on Debian because I don’t think it does.

evntdrvn

I've worked on maintaining internal dev tooling for some small companies for a while now, and it's a real PITA to write a robust installation script for bootstrapping a new laptop running an arbitrary shell on linux or macOS into a working environment. Way more work than it feels like it should be.

At this point I've pretty much given in and decided that a containerized dev environment is probably the better solution, but on principle it feels so unsatisfying to have to resort to this :(

(I know someone is going to mention Nix/Guix ;) but that feels like a giant rabbit hole)

t43562

FWIW in bash I have 2 functions:

  path_add() {
      export PATH=$PATH:$(string_join ':' $@)
  }

  path_prepend() {
      PATH=$(string_join ':' "$@"):$PATH
      export PATH
  }
These can join an arbitrary list of paths to PATH. e.g.

  path_add /usr/bin /usr/local/bin ~/bin
They depend on another one:

  string_join() {
      local join=$1; shift
      local result=$1; shift
      for p in "$@"; do
        result="${result}${join}${p}"
      done
      echo -n "$result"
      set +x
  }
I also have ones for adding and prepending to LD_LIBRARY_PATH

jlokier

The compact one-liners below are similar but avoid adding duplicate items to PATH, so are fine to call in various init scripts.

In Bash:

  path_append() { local p; for p; do [[ :"$PATH": =~ :"$p": ]] || PATH+=:$p; done; }
  path_prepend() { local p; for p; do [[ :"$PATH": =~ :"$p": ]] || PATH=$p:$PATH; done; }
In portable POSIX shell:

  path_append() { for p; do case :"$PATH": in *:"$p":*) ;; *) export PATH="$PATH:$p" ;; esac; done; }
  path_prepend() { for p; do case :"$PATH": in *:"$p":*) ;; *) export PATH="$p:$PATH" ;; esac; done; }

nsb1

I cribbed these from someplace - slightly different approach:

  ###################################################################
  # Add directory to path
  pathadd() {          
      newelement=${1%/}
      if [ -d "$1" ] && ! echo $PATH | grep -E -q "(^|:)$newelement($|:)" ; then
          if [ "$2" = "after" ] ; then
              PATH="$PATH:$newelement"
          else         
              PATH="$newelement:$PATH"
          fi
      fi
  }
 
  ###################################################################
  # Remove directory from path
  pathrm() {
      PATH="$(echo $PATH | sed -e "s;\(^\|:\)${1%/}\(:\|\$\);\1\2;g" -e \
      's;^:\|:$;;g' -e 's;::;:;g')"
  }

ceph_

I'm surprised both the blog post and all the other comments don't mention how it should have logic to check if the item exists in the path before adding it. Otherwise you get duplicates added everytime you source your config.

Your function does that so +1. Though I'd use

    [[ $PATH =~ "(^|:)$newelement($|:)" ]] 
over grep -q but it functions the same.

duped

In my (fever) dreams, there's a directory at `/var/share/env` with the rule that regular files are <file name>=<file contents> and all other file types are ignored. The `env` program can slurp all the files to create the default environment before applying whatever customization are in dotfiles for a shell.

Want to add to path? `echo ':<directory>' >> /var/share/env/PATH` (or `env --add PATH :<directory>`, or something like that).

evntdrvn

you could call it a "Registry"... ;)

unchar1

> fish instructions:

> set PATH $PATH ~/.npm-global/bin

Fish has some nice utilities for these type of set calls

set --append PATH ~/.npm-global/bin

Joker_vD

As for path duplication, I personally have

    printf '%s\n' "$PATH" | grep --color=auto -E -e '(^|:)'"$( printf '%s\n' "$1" | sed 's/[][\.|$(){}?+*^]/\\&/g' )"'($|:)' > /dev/null 2>&1
    if [ "$?" = 1 ]; then PATH="$1:$PATH" ; export PATH ; fi
in my version of pathadd().

t43562

.bashrc gets run every time a shell starts

I'm not sure I'd stick a path setting in .bashrc - just because it would make it very difficult to ever override that PATH setting elsewhere.

You might want that but it might also mess up some other program's attempt to set the PATH and then e.g. run a shell command.

I usually use .bash_profile or .profile more for this sort of thing and then I have to tell my terminal program to run bash as a login shell and that gives me what I mostly expect.

inetknght

t43562

So e.g. when I get an interactive shell from vim, for example, or an IDE it will execute .bashrc which can blow away settings I want for building.

bandrami

But not if the shell is running as an inferior process in an emacs buffer, in which case you have to do yet another thing.

whitten

Could you elaborate on the method ?

fuzzfactor

Here's a possible idea for an open-source project if someone is qualified.

Make it as easy as it is in Windows.

PaulKeeble

Its consistent on windows across shells but its also a hard to find feature if you haven't been using Windows for decades and its not easily scripted. Its still kind of hard, could be a lot easier than it is.

duped

It's in the registry, iirc. All Linux needs is a standard persistent key/value store(*) that by convention shells check for environment variables before running their rc scripts.

So it will never happen.

* the file system doesn't count

p_ing

SYSTEM: HKLM\SOFTWARE\CurrentControlSet\Control\Session Manager\Environment

User: HKCU\Environment

okkdev

It's not any easier on windows tho

the__alchemist

It is. You go to Env vars, and modify the PATH setting. It just works.

I'm confused on the Linux point, because I thought I was the only one who had problems. Until I saw this article! Describes it well. I take responsibility; I am bad at setting env vars in Linux.

mock-possum

I found Rapid Environment Editor for that stuff year ago, never looked back.

zabzonk

I'd second this - REE is a great utility.

01HNNWZ0MV43FF

I feel like it's harder in Windows. You mean, there should be a GUI to do it instead of .bashrc?

p_ing

Why do you feel it is harder in Windows? In Windows 11, go into the Settings app, search for 'Path', then choose "Edit environment variables <in system/in account>". You then get a graphical method to add/edit/delete entries, and the paths apply to any application you run. No need to find a dotfile to edit. No need to know how to format a particular entry, or chain it with the existing $PATH.

On macOS it is arguably harder as you need to use Cmd-Shift-. to reveal dotfiles should you be uncomfortable editing them with vim/nano/etc. When using Omz with Terminal, a path entry could be in any number of files supporting Omz along with my zshrc. Windows doesn't have this issue.

Windows has it's issues, but env vars are significantly easier to manipulate without much knowledge.

forgotpwd16

Don't even have to use GUI. Can do:

  setx path "%path%;C:\own\path\"
Sadly this has no error checking so it may mess things up. But it's an option.

Lanolderen

Tbh the moment you need to use a terminal is the moment you're googling the magic spell unless you've previously googled the magic spell.

On Windows you just go Win->start typing "environment variable" and you get "Edit the system environment variables - Control Panel". Kinda stupid that you then need to press "Environment Variables..." but I probably wouldn't have to google it.

forgotpwd16

The Windows has a system-wide variables (incl. PATH) applicable everywhere. In contrast, the Linux situation is touched in the submitted article.

>All of these directions only work if you’re running the program from your shell. If you’re running the program from an IDE, from a GUI, in a cron job, or some other way, you’ll need to add the directory to your PATH in a different way, and the exact details might depend on the situation.

>I’m honestly not sure how to handle it in an IDE/GUI because I haven’t run into that in a long time, will add directions here if someone points me in the right direction.

The ~/.pam_environment was the equivalent to Windows' environment variables but has been deprecated and supposedly systemd's environment.d took over its functionality.

dingnuts

I don't know what she means about setting the PATH for a GUI. If you want to run a program from the GUI you add a .desktop file to ~/.local/share/applications and provide the full path to the binary as part of the Exec key[0]

You can also override the actual PATH variable for an application in the .desktop file as well, or any environment variable, with the Environment key, if that's what she means.

0 https://specifications.freedesktop.org/desktop-entry-spec/la...

jillyboel

you can just stick it in /etc/environment and reboot if you want

zokier

The problem is that Linux does not have solid way of setting environment variables for an user (or user session). .bashrc is relevant for only bash (obviously), but bash is not the only thing that needs PATH and other env vars.

At least systemd brought some sanity with environment.d, but as this thread (and jvns article) shows it's not very well known. And of course there is still all sorts of weird inherited complexity like pam_env.

mock-possum

There should certainly be a GUI to do it.

p_ing

The difference on Linux/BSD/macOS is that the Window Server is a userland application -- you can't depend on it existing (except macOS), nor can you depend on the underlying shell being consistent on a given OS/distribution (macOS ships with a deprecated bash shell and current zsh shell).

On Windows, Microsoft has it easy. The window server is part of the executive; Win32/conhost always exist, even if you choose to run another personality (OS/2, POSIX, et. al.).

I would bet that there is a form of graphical editor somewhere out there on the Internet that someone developed for fun for a given shell.

fuzzfactor

>You mean, there should be a GUI

That must have been what Microsoft was figuring back in the 20th century.

Even in the latest W11, it's the same basic everyday GUI as in Windows XP :)

Just a little more intuitive now.

In XP from the Control Panel click System > Advanced > Environment Variables.

In Win11 from Settings click System > About > Advanced System Settings > Environment Variables.

Pay attention to your choice of User PATH, in addition to System PATH.

Make sure that things like %HOMEDRIVE% that you edit in, actually appear as "C:" once you are done editing, or you're not doing it ideally.

sieabahlpark

You haven't edited env vars in the path since win 8 then. At some point they made a GUI for env vars and the path variable.

saint_yossarian

That GUI has been there since at least XP.

tpoacher

[flagged]

MisterTea

All this path stuff would go away if you have a proper vfs where you bind you other bins over /bin allowing $path to simply read "/bin ." and be done with it.

gruez

How does this work in a multi-user environment? Or if you want to override PATH only in certain contexts (eg. python virtual environment, which overrides python and pip)?

MisterTea

Per-process namespaces which gives each process its own table of mounts and binds. This is how Plan 9 works where you setup the environment with a script or via calls to mount(2) and bind(2). When you log in your profile sets up all your mounts/binds in your root namespace and every process you create thereafter inherits them. Then you change these mounts/binds as needed.

forgotpwd16

Fwiw, this can be imitated on Linux with `unshare` or/and `nsenter`. Another brick to making a poor Plan 9 clone.