Hacker News new | past | comments | ask | show | jobs | submit login
/.bashrc VS –/.profile VS –/.bash_profile (leimao.github.io)
140 points by keyboardman on Oct 24, 2020 | hide | past | favorite | 36 comments

I've been doing Unix for 30 years now and for the last 27 I've had all these scripts wired together. Or rather I have one script, then the others execute it.

I get in theory why I'd want interactive login shells to have a fancier / more complex environment than others. Maybe I should care more about optimizing startup time of non-interactive shells. But really, who has the time to figure that stuff out?

It's simple enough.

Put env vars in bash profile and functions, aliases and other non-env (completion, nvm, rvm etc) in bashrc.

Put the latter into the former and they aren't inherited. Put the former into the latter and path appends and other non-idempotent edits to env vars don't work correctly. Though I personally use some functions to make sure my path edits are idempotent for ease of iteration when editing.

(Oh and you want to source your .bashrc from your .bash_profile so you start out with both env vars and everything else.)

> It's simple enough.

> (Oh and ...)

+1 to just dumping it all in ~/.bashrc

  . ~/git/stuff/bash_common.sh

Interesting on the docker angle. Sometimes seeing the flow visually can help people:



That is... helpful, in a sense. I mean, it's not the fault of the graphic that it's so convoluted.

But can someone explain why it has to be this convoluted? If I parse it correctly, there is no single place where I could place an alias and have it be available in all bash invocations (e.g. green and yellow arrow don't go through any common files). Without resorting to sourcing one from another, that is.

Is there a method to this madness?

Actually, it is the fault of the graphic.

One flowchart works like a conventional flowchart but shows all of the command-line overrides as separate decisions. They are, but that obscures the actual simplicity of the system. The other flowchart has an odd way of representing the decisions, not using decision nodes. And the colouring isn't even consistent, and is downright wrong in one place.

There are really only four possibilities: login interactive shell, login non-interactive shell, non-login interactive shell, and non-login non-interactive shell.

Whether a shell is login/non-login, and whether it is interactive/non-interactive, are determined from a combination of things including standard I/O file descriptors and process arguments; standard-conformant mode differs from native mode in almost all shells; and shells like the Z shell have several compatibility modes for other shells; but there are just four outcomes, one of which (login non-interactive shell) is rare in practice (https://unix.stackexchange.com/q/304447/5132).

And the basic idea is that personal convenience shell aliases and shell functions, key bindings, prompts, completion controls, and all of the other UI parphernalia such as history files and mailboxes to monitor, are things that you should only be setting for interactive shells. For the Z shell in its native mode, for example, that's the domain of the global zshrc and the per-user .zshrc . For the Korn and Almquist shells, similarly, that's the domain of the file named by the ENV environment variable. For the C shell it is the domain of the global cshrc and per-user .cshrc .

You probably don't want to set aliases in all bash invocations, as they can royally screw scripts.

For interactive shells the rc file is the proper place.

I’ve more often had my cronjobs screwed because they didn’t have the same aliases and PATH variables, rather than have them screwed because there was an alias for « ll ».

The dichotomy between login and non-login environments is source of hours spent debugging, especially for beginner to average developers.

The former image is from a blog post entitled "Shell Startup Scripts" [0] which I've just submitted [1] separately because it's so much more informative (it's been posted to HN previously but never really provoked much discussion).

That blog post was my primary reference after zshall(5) when I decided to blow away the years of cruft I'd built up, start over, and rewrite my (Zsh) shell init files from scratch -- and I've been amazed at how much more organized and manageable it all is since then!

Prior to switching to Zsh, I had gotten to the point of just dumping everything in one file and making sure it was sourced (by bash) in all instances.


[0]: https://blog.flowblok.id.au/2013-02/shell-startup-scripts.ht...

[1]: https://news.ycombinator.com/item?id=24881944

Would be helpful if there was a legend. What do the colours mean in those diagrams?

They mean the diagram is useless, and quite possibly wrong. What is going on with zsh??

I interpret it as "follow the arrow that goes through the choices representing your situation". It would've helped to differentiate the "choice" bubbles from the "this file is sourced" bubbles.

I try to maintain a cross platform set of bash startup scripts but the biggest problem is that macOS and Linux don't follow the same rules.

.bash_profile is only supposed to run after logging into an interactive shell, and .bashrc is for every new shell.

But macOS runs .bash_profile every single time you open a new shell window, so you have to do some creative scripting to make it do what you want to do.

I use

  OS=`uname -s`

  if [ "$OS" = "Darwin" ]
     # all the mac stuff

     add_plist () {
       /usr/libexec/PlistBuddy -c 'Delete :'"$1" ~/.MacOSX/environment.plist >/dev/null
       /usr/libexec/PlistBuddy -c 'Add :'"$1"' string "'"$2"'"' ~/.MacOSX/environment.plist >/dev/null

      if [ ~/.bashrc -nt ~/.MacOSX/environment.plist ]
        mkdir -p ~/.MacOSX
        add_plist PATH "$PATH"
        add_plist HISTSIZE "$HISTSIZE"
        add_plist HISTCONTROL "$HISTCONTROL"
        add_plist PS1 "$PS1"

remove the - in front of bash in the settings of Terminal.app. Then the terminal does not run a login shell but an interactive shell. You might have to add -i after bash for that.

Why not just guard your .bash_profile with something like

    if shopt -q login_shell; then
and then just pretend it’s the Linux-y way?

My solution is that bash_profile simply runs bashrc, and bashrc starts with:

if [ -n "$PS1" ]; then

That seems to work fine everywhere.

I agree, this is a much better solution. Multiple shell configuration files should be deprecated and replaced with this.

I generally use .profile as a place to declare configuration common to all shells, mostly environment variables like PATH and GOPATH and the XDG_*_PATH variables. Then I use shell specific files like bashrc and zshrc to configure shell-specific things, and make sure to source .profile.

I use .profile for exporting environment variables and .rc for everything else.

The rationale is that exported env variables are inherited (and I most probably don't want them overwritten by child interactive shells reading .*rc) and other things (e.g. aliases) aren't and I probably want them set for every interactive shell.

That's the theory, but desktop environments often won't run your `.bash_profile` or `.profile` (so you won't end up with environment variables set in graphical apps), and then most terminal emulators don't run login shells by default (so you won't end up with environment variables set in your shells either). And even if your terminal emulator runs a login shell, you'll only have environment variable set in your shells, which will lead to problems when graphical apps don't have the same variables set.

Just put a "echo ran bashrc" and "echo ran bash_profile" in each file. After a couple of weeks you'll have it all figured out.

As a non Linux admin I struggle to understand why you would want the differential between a login and a non login script.

One interesting thing I discovered was there are system-wide profile and bash profile scripts.

/etc/profile /etc/.bashrc

In case you need to set these for every account.

This article does a fair bit of explaining: [1]

> The primary thing to understand is that the rc files are for all shell invocations while the profiles are strictly for interactive shells. An interactive shell is where you (end user) types the command while an non-interactive shell is when shells are launched by other programs such as a script with #!/bin/bash as SHEBANG value

[1] https://www.golinuxcloud.com/bashrc-vs-bash-profile/

So, what are use cases in which a non-interactive shell is launched? One example is a cron jobt that runs every night. It's basically the "cron" process executing a command which could be a bash script. Another example would be startup scripts that launch background processes and services which you'd find in /etc/system.

There's a distinction between interactive v. non-interactive and login v non-login as well [2]


Typical issue would be adding something to your PATH variable in .profile, having your script work just fine in a shell, but having it fail when you run it as a cron job because .profile never gets sourced by cron.

These days, I fail to see any need to put anything in the .bashrc file.

If there is some non-interactive use of bash, I just want a clean environment, and everything scp or ssh needs should be on the standard $PATH.

There's no point in putting anything in .profile either, because if I am running an interactive session, I will be using bash. I don't care about the config for dash or whatever.

And any scripts should completely specify their required functions, $PATH and whatever inside the script itself. Use 'source' to pull in common functions.

In Linux, processes scheduled in cron, systemd or other schedulers don't require and may sometimes have issues with the customizations in login scripts for interactive sessions. Login scripts may expect input from the user keyboard, or may depend on environment variables that exist when a login script is executed, but not when nonlogin scripts execute.

Schedulers run non-interactive non-login shells, which don't load any of these files.

Often people have login scripts that print things like how much mail you have, etc.

That's not necessary or desirable in a non-login context.

Just don't try to apply anything you learn here to GitHub Actions images because the environments are kind of wild and broken across OSes and your scripts will not be triggered the way you would otherwise expect them to...

These rules of thumb are useful, but in the real-world I find the use of startup files to be very inconsistent across different platforms. Oftentimes I someone will override the default behavior, sourcing files in different conditions or order than you would expect. There's really no substitute to reading all the startup files in /etc and ~. Sometimes it's even necessary to inspect the binary itself when you find that a shell has been compiled with non-standard paths.

i'm a huge fan of bashrc_dispatch, which splits your bashrc into versions that run either all the time, when non-interactive, when interactive, or for login shells, as well as gives you functions like `shell_is_osx`, so you can share your configs across multiple operating systems:


For anyone confused by the title: the dashes are supposed to be tildes.

/.bashrc is missing the dash too...

Thank you!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact