userpath: wrong shell
@ofek userpath doesn’t work for my setup locally so I’m having some trouble testing.
Here are my dotfiles in case it helps: https://github.com/AlJohri/dotfiles
Issues:
- current shell is ZSH but it appends to bash files
- appends to both ~/.bashrc and ~/.bash_profile instead of just doing the right thing based on the OS
$ userpath append /testinguserpath
Success!
$ echo "$?"
0
$ userpath verify /testinguserpath
The directory `/testinguserpath` is in PATH, pending a shell restart!
$ echo "$?"
2
$ echo "$SHELL"
/usr/local/bin/zsh
$ tail -n 2 ~/.bash_profile
# Created by `userpath` on 2019-05-10 15:39:32
export PATH="$PATH:/testinguserpath"
$ tail -n 2 ~/.bashrc
# Created by `userpath` on 2019-05-10 15:39:32
export PATH="$PATH:/testinguserpath"
~/.zprofile and ~/.zshrc were untouched
About this issue
- Original URL
- State: open
- Created 5 years ago
- Comments: 21 (5 by maintainers)
bashBackgroundRant mode ON. 🙀
Just FYI (Unix geek here), I think bash is broken by design when it comes to startup files. This is especially true in big university multiuser Unix installations, which is my own background. For proof, run
man bash, look under the “INVOCATION” section, and prepare to have your mind blown. Compare and contrast this withman zshand look under “STARTUP/SHUTDOWN FILES”, for a much less insane approach to system and user configuration. IMHO. 👀Red Hat Enterprise Linux, Ubuntu, and probably other OSes or distros attempt to work around the bash problem by providing clever dotfiles in “/etc/skel” – these are dotfiles that are copied into a new user’s home directories by default when the new user is created. These default dotfiles are supposed to create a complex structure where each user has a ~/.bash_profile (OR ~/.profile on Ubuntu) which always sources the user’s ~/.bashrc. That way, both dotfiles will be executed for login shells, and the bulk of the configuration can be put into ~/.bashrc. On Red Hat, ~/.bashrc IN TURN also sources /etc/bashrc. To repeat;
~/.bash_profilesources~/.bashrcsources/etc/bashrc. You could say that on Red Hat, Ubuntu et. al. it would be considered broken behavior NOT to create these default dotfiles in a new user’s home directory.Take a wild guess if Apple creates bash dotfiles in users’ home directories on macOS? Of course not. 🤦 Instead, as @AlJohri noted above, they just run every Terminal window as a login shell (!). 🤦
I’m just happy Apple finally decided to switch from bash to zsh starting with macOS 10.15 Catalina, so the problem with bash will eventually go away there.
Rant mode OFF. 😉
Solution?
I believe the best way to treat bash users is probably to assume that the user will encounter at least one “login shell” somewhere during the login process before running userpath, and add our PATH definition to the first dotfile for bash login shells that we find. The reason I say this is that these files are also where Red Hat, Ubuntu and others suggest that users should add their own $PATH definitions. To quote from the manual, a bash login shell:
Note — ONLY from the first one. If a ~/.bash_profile exists, that will disable the other two files, as far as bash is concerned.
What to do if none of the login dotfiles exist? That’s a trickier question.
To sum up… This is my opinion, but I’m basing it on a lot of experience with Unix shell scripting and user support at university Unix systems. I don’t believe that there’s a single “correct” solution for bash. But we can try to be as helpful as possible, at least.
I’ll be happy to answer any additional questions about this subject. 😄
I think we should handle the cases that are known. I would also like to see a CLI option and environment variable where I can manually define the file I want the PATH to go in. For example, I personally use
~/.shprofile. It would be great to specifyUSERPATH_FILE=$HOME/.shprofileand have it automatically use that regardless of she shell along with cli arg such asuserpath --fileHere’s my thoughts so far on detection using OS+Shell:
general note on bash quirk:
in bash, the login-shell only runs the
bash_profileorprofilewhile a non-login shell only runs the bashrc.this means, that an interative, login shell will not automatically source bashrc. other shells such as zsh do not have this strange behavior. in zsh, if its an interactive shell, it will run zshrc regardless of login or non-login. this is why only bash requires sourcing bashrc from the profile.
for bash, there have two approaches on non-macos systems:
modify BOTH
bash_profile/profileandbashrcso future shells in the current login will have the PATH enabledmodify ONLY the
bash_profile/profile, set the PATH for the current session, and tell the user the PATH will not be available in future terminal sessions until they log out and log back inI’m personally more in favor of the latter but I don’t work on linux systems everyday so open to feedback here. I think it’s bad practice to populate PATHs in two places and would like to avoid that as much as possible.
general note on /etc/profile quirk:
on macos, arch linux, and potentially other distributions, the
/etc/profilesets the initial profile https://stackoverflow.com/questions/21038903/path-variable-in-zshenv-or-zshrcthis gets run after
~/.zshenv. thus while zshenv seems like the ideal place to set the path since it is always sourced, it has quirks on some systems as the path gets overidden.Rules
macos: on macos, every new terminal session is a login shell. the initial path gets set by
/etc/profile(which is run after zshenv). thus, we set the path using bash_profile or zprofile.macOS + bash = bash_profilemacOS + zsh = zprofileubuntu: on ubuntu, every new terminal session is a non-login shell. the default setup presents a
.bashrcand.profile(where the.profilesources the.bashrc). on ubuntu the/etc/profiledoes not set an initial PATH sozshenvis fair game.ubuntu + bash = profile/bash_profileand request user to re-login to enable in future terminal sessionsubuntu + bash = bashrc + profileif asking to re-login is not viableubuntu + zsh = zshenvprofile/bash_profilemeans use whichever file already exists, defaulting toprofile. this with @ned2’s issue aboveany other linux distribution (such as arch)
linux + bash = profile/bash_profileand request user to re-login to enable in future terminal sessionslinux + bash = bashrc + profile/bash_profileif asking to re-login is not viablelinux + zsh = zprofileand request user to re-login to enable in future terminal sessionslinux + zsh = zshrc + zprofileif asking to re-login is not viableprofile/bash_profilemeans use whichever file already exists, defaulting tobash_profile. this with @ned2’s issue abovefish
macos + fish = ~/.config/fish/config.fishset -U fish_user_paths /usr/local/bin $fish_user_pathssince this always works, and you don’t need to find the config file. see$PATHsection http://fishshell.com/docs/current/tutorial.htmlI think the latter makes more sense for fish users.
Open to feedback! I’m a macOS user so I mostly care about the macOS rules but I think having a smart set of defaults that works for most users and is overridable/configurable when needed is best.
I agree with modifying a single file and having the user re-login.
Some Example Docker Commands to Verify the Above Notes:
macOS
Ubuntu
Arch
Fedora
@AlJohri It will (usually) be 2 files per shell: 1 for login (
-l/--login) & 1 for non-login shells. What #6 did was add support for more shells, and make it so it won’t update files for everything (bash, zsh, fish, etc.) but rather based on your current shell (or any you select).If you want, I could add another flag
--login/--non-loginwhich would default to both. Keep in mind if one of your shell configs does not source the other, you really do want both updated 😄A related issue in that it varies across OSes which I just uncounted (and which could be relevant to your situation @jaraco):
Ubuntu defaults to providing a .profile (and not a
.bash_profile), which I’ve been using as.bash_profilemight often be used. Perhaps this is not best practice, but I suspect a lot of people do this by virtue of Ubuntu’s initial setup.The catch is that if a
.bash_profileis present, then.profilewill not be sourced. So the default behaviour ofuserpathautomatically adding a.bash_profileblocks an existing.profilefrom being sourced. Took me a little while to workout why my config was broken.What is “everything” here?
I vote to form a heuristic to determine the best single file to modify (i.e. SHELL and OS), and to only modify that single file if there is enough confidence from the heuristic.