Make it as easy as it is in Windows.
So it will never happen.
* the file system doesn't count
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.
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.
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.
>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.
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.
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/
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...
Not necessarily. Bash won't do it on its own for a login shell, but there's probably a bit in the /etc/profile your distro provides that does it.
* 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.
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.
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_PATHIn 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; } ###################################################################
# 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')"
}"export PATH=$DIR:$PATH - That particular pattern is way too common, and is very dangerous if you consider the case when [$DIR or] $PATH (or whatever your variable is, like $LD_LIBRARY_PATH) isn’t set. Then, the value will be :/path/to/dir, which usually means both /path/to/dir and the current directory, which is usually both unexpected behaviour and a security concern."
Your function does that so +1. Though I'd use
[[ $PATH =~ "(^|:)$newelement($|:)" ]]
over grep -q but it functions the same.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.
False things people believe.
https://stackoverflow.com/a/820533
Probably because it's sourced from your distro provided /etc/profile. Bash will only source .bashrc on its own in some cases.
Want to add to path? `echo ':<directory>' >> /var/share/env/PATH` (or `env --add PATH :<directory>`, or something like that).
function my_env() {
ENVDIR=${ENVDIR:-/var/share/env}
arg="$1"
case "$arg" in
--add)
echo "$3" >> $ENVDIR/"$2"
;;
*)
(
for f in $ENVDIR/*; do
export $(basename $f)="$(< $f | sed -z 's/\n:/:/g')"
done
[[ -z "$arg" ]] && env || env "$arg"
)
;;
esac
}
(Of course, that's a bad approach but your idea is certainly doable in a good way.)I attempted this in bash like so:
``` PROMPT_COMMAND="history -a; $PROMPT_COMMAND" ```
But, that meant every shell was sharing the same history. Pressing up would go to the last command run anywhere, not just my current shell.
Sadly, I wasn't even trying to solve the problem Julia is talking about here. I just wanted to make sure my history was saved when I shutdown. Still haven't found a great solution to that. My attempts as using traps and signals caused weird issues.
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)
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().> set PATH $PATH ~/.npm-global/bin
Fish has some nice utilities for these type of set calls
set --append PATH ~/.npm-global/bin