Bash not sourcing ~/.bashrc

Hello!

I’m used to source user-specific ~/.bashrc file when starting interactive Bash shell. However, surprisingly, my freshly-installed NixOS 22.05.3893.040c6d8374d (Quokka) does not want to do it automatically.

  1. Is there any reason behind that?
  2. What should I do to allow users to source their .bashrc files and not use Home Manager for that?
  3. Why nobody else have this problem?? :confused:

I’m having fun discovering NixOS philosophies :wink:

3 Likes

Hello!

Are you sure .bashrc is not sourced at all, e.g. rather than breaking early on because of an incompatibility? Because you shouldn’t need to do anything to have it sourced (assuming you don’t pass --norc to bash somehow). I would start by sticking an echo to the very beginning of .bashrc to verify if it’s actually evaluated.

1 Like

Hi there, thank you for reply.
I’m pretty sure that .bashrc is not sourced at all.

I did several investigation steps I describe them below:

  1. I do login to NixOS machine via SSH as regular user
  2. then I switch to root via sudo -i
  3. in both situations no .bashrc is sourced
  4. when I do source ~/.bashrc manually it is valid and is being sourced (I can see result of debug echo command, my $HISTCONTROL is being set, shortcut key is being bound.

Additionally, I did some nifty check:

$ bash -lixc exit 2>&1 | grep -i bashrc

and it shows only this:

+ . /etc/bashrc
++ __ETC_BASHRC_SOURCED=1
++ test -f /etc/bashrc.local

I’m stuck and very surprised as this is very fresh install of NixOS.

2 Likes

You’re invoking it as a login shell there, which explicitly does not source ~/.bashrc. From the bash man page:

When bash is invoked as an interactive login shell, or as a non-interactive shell with the –login option, it first reads and executes commands from the file /etc/profile , if that file exists. After reading that file, it looks for ~/.bash_profile , ~/.bash_login , and ~/.profile , in that order, and reads and executes commands from the first one that exists and is readable. The –noprofile option may be used when the shell is started to inhibit this behavior.

When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc and ~/.bashrc , if these files exist. This may be inhibited by using the –norc option. The –rcfile file option will force bash to read and execute commands from file instead of /etc/bash.bashrc and ~/.bashrc .

Strangely, there are some references to bashrc in your logs. I suspect they’re loaded by a profile for one reason or another. I’m also not sure if -x is a good tool for tracing whether bash sourced initial login files, not wholly convinced it’s doing that through commands that will be logged there.

By default the shell that you load into from the TTY is a login shell as well, which is probably what’s confusing you. Some people manually add a source ~/.bashrc to their ~/.bash_profile, you may have done so previously and forgotten about it. Or you just rarely use the TTY directly, terminal emulators usually run non-login interactive shells. Saw the SSH reference, sorry, skimmed past that. No idea why your SSH session isn’t reading bashrc (I forget if those are login shells too, or if you customize that behavior, or what), but your test definitely should not.

Bash config always seems trivial until you spin up a new machine :stuck_out_tongue:

6 Likes

Good catch :slight_smile: I do have [ "$BASH" ] && [ -f "$HOME/.bashrc" ] && source "$HOME/.bashrc" at the end of my ~/.profile.

I think the expectation is most likely coming from Debian or its derivatives, where /etc/profile will source /etc/bash.bashrc and the stock ~/.profile will source ~/.bashrc[1].

  1. https://sources.debian.org/src/base-files/12.3/share/dot.profile/
4 Likes

@TLATER you were right! I looked now at my .bash_profile on Arch machine and it looks like that:

[[ -f ~/.bashrc ]] && . ~/.bashrc

Until today I have always relied on .bashrc as the main file, but thanks to this discussion I’ve learned something new :slight_smile: Thank you!

1 Like

:expressionless: I wonder how this became a thing, given ~/.profile is considered the shell-independent file in which generic variables are defined. zsh & co probably won’t be happy loading ~/.bashrc.

I’m guessing it’s historical, and that once upon a time there was no ~/.bash_profile, but this outcome just seems bad…

1 Like

I perpetually find the squishy norms around shell init files confusing. They also get handled differently on macOS (though IIRC it’s more about how macOS vs linux handle the notion of login shells?), and people with multiplatform dotfiles can further subvert the platform norms.

1 Like

I believe .bash_profile used to be automatically created, but something has changed. I think this issue is related. I commented there on the new behaviour.

Are you sure?

If that changed, it hasn’t been the case since around late 2019. To my knowledge I’ve never had a .bash_profile in any of my NixOS systems. I think I’d have noticed, I use zsh and hate dotfile clutter in my home directories.

I might be overlooking something, but I don’t see any evidence of such a feature over the last few years in the perl script that does user creation: https://github.com/NixOS/nixpkgs/blob/a17f99dfcb9643200b3884ca195c69ae41d7f059/nixos/modules/config/update-users-groups.pl

Also don’t think there are any options for skel directories, and I don’t think there’s a default one.

Might make sense to spin up a VM with your config and build an older nixpkgs commit just to confirm?

Wayland sessions don’t currently load your shell init files, depending on whether you use a login manager/which login manager you use, though, which I suspect is more likely the root cause of that ticket?

I found this topic when investigating why my .bash_history recently started being reset to a tiny number of lines once in a while. Based on the above I’ve done a simple test, and it turns out ssh 127.0.0.1 does not source ~/.bashrc. Verified this by adding set -x on top of ~/.bashrc, and nothing extra was printed when SSH-ing in. This means HISTFILESIZE and HISTSIZE are both set to tiny values of 500.

I’ve also verified that SSH sessions are interactive:

$ echo $-
imBETs
$ ssh 127.0.0.1
Last login: Fri Sep 15 20:05:29 2023 from ::1
$ echo $-
imBEHTs

Something must’ve changed recently (as part of or since 23.05 would be my guess), because I regularly SSH between my machines, and this didn’t use to be a problem.

I’ll try working around this by setting programs.bash.interactiveShellInit to my .bashrc contents; maybe that helps. Update: Yep, that did the trick.

1 Like

Again, from the bash manual:

When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable.

When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc and ~/.bashrc, if these files exist.

The ssh shell is an interactive login shell, so it behaves according to the first paragraph, i.e. it should not execute your .bashrc. This is expected and documented behavior, and has been for decades.

Login shells are shells that start your session on a given computer, and are not subshells of another shell. This is pretty much only those launched by ssh or your tty login, so never the shells in your emulator in a graphical session. Interactive shells are those attached to terminals where you are interactively typing, and may also be login shells.

The reason these operation modes are often confused is because people rarely know the difference between login and interactive, and because debian’s default skel for some cursed reason loads .bashrc from .profile, defying bash’ default behavior and potentially breaking non-bash shells.

NixOS has never screwed with this behavior. I imagine some people copy around their old debian shell init files, and then get confused when behavior changes if they rewrite them from scratch.

macOS, at least in the official Terminal app, also plays a role in the confusion here by launching login shells.

Not sure about the sanity of the approach, but at some point I got annoyed and decided to just set up .bash_profile to source .bashrc, and set up .bashrc to bail out after basic PATH setup when it isn’t interactive.

:person_shrugging:

1 Like

I think that’s pretty reasonable for user config. It’s 2023, nobody actually uses .bash_profile to start daemons anymore, that’s for systemd user sessions. I do think the upstream bash behavior deserves a rework, precisely because it’s so confusing, but I don’t think debian is doing us any favors by doing so at a distro level.

2 Likes

I guess the question then becomes "LOL WTF Bash, where am I supposed to set HIST*SIZE and the like so that an SSH session works the way 98.7253% of users expect it to - that is, the same as just opening a bloody terminal on the relevant machine?

2 Likes

I’m sure that’s a hypothetical question, but I like @abathur’s suggestion a lot.

The problem is that we want to execute the interactive config (which should probably be in .bashrc) in interactive login shells, but it’s not done by default.

For completeness sake, and for those who may land here in the future, I would probably do this personally:

# ~/.bash_profile

# Set up any env vars, etc.

[[ $- == *i* && -f "$HOME/.bashrc" ]] && source "$HOME/.bashrc"

And then make sure $PATH and other variables are set correctly on session login, and carried over into all interactive shells I care about, instead of modifying them every time I launch an interactive shell. Mainly to keep modifications post session-setup correctly reflected in subshells, as might happen with wayland/X init scripts joining the mix.

But @abathur 's lazy suggestion is nice if you don’t trust env vars to carry everywhere properly (e.g. in emacs shells):

# ~/.bash_profile

# Run things that should happen on user login, but before
# X/wayland is started, e.g. star your emacs daemon maybe,
# though really you should use systemd for these things

source "$HOME/.bashrc"
# ~/.bashrc

# Set up any env vars, etc.

[[ $- == *i* ]] && exit 1

# Do interactive shell initialization

Maybe this should be bash’ default behavior, but it’s overall still brittle. Really someone needs to sit down and think through the whole modern session init design, all the way from tty login over systemd units to wayland session, and then raise a crusade of PRs to all projects involved…

1 Like

I suspect it’s more realistic for everybody to just start using modern shells. Bash is too widespread to change any defaults.

1 Like