In bash, which startup files does a login shell read compared with an interactive non-login shell, and why does almost every ~/.bash_profile contain a line that sources ~/.bashrc?
answer
- two axes, not one flag
- profile files versus the rc file
- first match wins among three
- one sourced line unifies both paths
basics
~10 sA login bash shell reads /etc/profile and then the first existing of ~/.bash_profile, ~/.bash_login or ~/.profile. An interactive non-login shell reads ~/.bashrc instead. Sourcing ~/.bashrc from ~/.bash_profile makes both entry paths behave identically.
solid answer
~40 sBash picks its startup files from two independent properties: whether it is a *login* shell (started with `-l`, or with an argv[0] beginning with a dash, which is what `login` and `sshd` do) and whether it is *interactive*. A login shell reads `/etc/profile`, then the **first readable** file among `~/.bash_profile`, `~/.bash_login`, `~/.profile` — first match wins, the others are skipped — and `~/.bash_logout` on exit. An interactive non-login shell (a new terminal tab, a `screen`/`tmux` pane, `bash` typed inside a shell) reads `~/.bashrc` only, plus a system file such as `/etc/bash.bashrc` on Debian-family builds. Because the two paths share nothing, anything you want in every session would have to be duplicated. So `~/.bash_profile` conventionally does `[ -f ~/.bashrc ] && . ~/.bashrc`, making `~/.bashrc` the single place where prompt, aliases, `shopt` and completion live.
go deeper
Know that ~/.bashrc is where aliases and prompt settings go and that it is read by new interactive shells, while ~/.bash_profile belongs to login sessions such as SSH. Be able to say why people source one from the other.
Explain the login/interactive matrix and the exact file order, including that the three profile candidates are a first-match search. Say which kinds of settings are inherited by child shells and which are per-shell.
Show how you diagnose a startup problem rather than guessing: --norc/--noprofile/--rcfile to isolate, bash -lxc exit to trace, and shopt login_shell plus $- to classify a live session. Explain why the split causes duplicated PATH entries.
Own the fleet-wide policy: whether machine-wide settings belong in /etc/profile.d fragments or in configuration management, how to keep per-user rc files from silently breaking automation, and why a slow ~/.bashrc taxes every command run over SSH.
## Two questions bash asks at startup When bash starts it classifies itself along two axes, and the combination — not one flag — decides which files it reads. **Login shell.** Bash is a login shell when it is invoked with `-l` or `--login`, or when the program that executed it passed an `argv[0]` whose first character is a hyphen (you see this as `-bash` in `ps`). `login(1)` and `sshd` both do the hyphen trick, so a console login and an interactive `ssh host` session both give you a login shell. **Interactive shell.** Bash is interactive when it was started without a script argument and its input and output are attached to a terminal; it then prints a prompt and enables job control and line editing. Interactive shells have `i` in the special variable `$-`. The two are independent. You can have a login-and-interactive shell (SSH session), an interactive-non-login shell (a new terminal tab on most Linux desktops), a login-non-interactive shell (`bash -lc 'cmd'`), and a plain non-interactive shell (`bash script.sh`). ## What a login shell reads 1. `/etc/profile` — the system-wide file. On nearly every distribution it ends with a loop that sources `/etc/profile.d/*.sh`, which is where packages drop system-wide environment fragments. 2. Then the **first** of `~/.bash_profile`, `~/.bash_login`, `~/.profile` that exists and is readable. This is a first-match-wins search, not a chain: if you have both `~/.bash_profile` and `~/.profile`, only `~/.bash_profile` runs. This trips people who keep their environment in `~/.profile` (shared with `dash` or `sh`) and then create a `~/.bash_profile` for one bash-specific line — the rest of `~/.profile` silently stops running. 3. On exit, `~/.bash_logout` (and a system equivalent on some builds). `--noprofile` suppresses this whole step. ## What an interactive non-login shell reads Only `~/.bashrc`, and on distributions that compile bash with a system rc file (Debian and Ubuntu) `/etc/bash.bashrc` first. Red Hat-family systems achieve the same by having the stock `~/.bashrc` source `/etc/bashrc` itself. Such a shell never touches `/etc/profile` or `~/.bash_profile`. `--norc` suppresses it; `--rcfile FILE` substitutes a different file, which is the clean way to test a config without editing your own. ## The third case, briefly A non-interactive, non-login bash reads none of the above. It expands the value of `BASH_ENV` and, if that names a readable file, sources it. This is why environment set only in `~/.bashrc` is missing from `cron` jobs and from `ssh host cmd`. ## Why the source line exists The historical split assumed a login session started once and spawned everything else, so the profile set exported variables (inherited by every child) and `~/.bashrc` set per-shell things that are *not* inherited: aliases, shell functions, `shopt` settings, the prompt, readline bindings, completion specs. Modern desktops broke the assumption — a terminal emulator typically starts an interactive non-login shell, so the profile may never run in a whole day of work. The conventional fix is one line at the end of `~/.bash_profile`: ```bash [ -f ~/.bashrc ] && . ~/.bashrc ``` Now both entry paths converge on `~/.bashrc`, and you maintain one file. The complementary discipline is to keep `~/.bashrc` fast and side-effect-free at the top, because it now runs for every shell. ## Which setting belongs where - Exported environment (`PATH`, `EDITOR`, `LANG`): the profile is the classic home, since children inherit it and it need only run once. Putting it in `~/.bashrc` also works but re-runs on every nested shell, which is how `PATH` ends up with duplicate entries. - Aliases, functions, prompt, `shopt`, `complete` specs, readline `bind` commands: `~/.bashrc`, always. They are per-shell state and are not inherited. ## Telling which shell you are in ```bash shopt -q login_shell && echo "login shell" case $- in *i*) echo "interactive";; *) echo "not interactive";; esac ``` `echo $0` showing `-bash` is the other classic tell. When startup behaviour is mysterious, `bash -lxc exit` traces exactly which files a login shell executes, in order.
- How would you check, from inside a running shell, whether it is a login shell and whether it is interactive?`shopt -q login_shell` returns success for a login shell, and bash sets `login_shell` at startup only. For interactivity, test the special variable `$-` for an `i` flag: `case $- in *i*) ...;; esac`. Informally, `echo $0` printing `-bash` also indicates a login shell, because the leading hyphen in argv[0] is how the parent process requested one.
- A user keeps their environment in ~/.profile, then adds a small ~/.bash_profile. Their settings stop applying. Why?A login bash reads only the first readable file among `~/.bash_profile`, `~/.bash_login` and `~/.profile` — it is a first-match search, not a sequence. Creating `~/.bash_profile` makes bash stop reading `~/.profile` entirely. The fix is to source it explicitly from `~/.bash_profile`, keeping `~/.profile` as the portable, sh-compatible part.
- How do you test a change to a startup file without breaking your current session?Start a throwaway shell with the file you want: `bash --rcfile /tmp/test.bashrc -i` for rc changes, or `bash --noprofile --norc` for a clean baseline. To see what actually runs during a login, `bash -lxc exit` prints an execution trace of the profile chain. None of these disturb the shell you are already sitting in.
saying these in an interview costs you the question
- Says a login shell reads .bash_profile and then .bashrc automatically
- Believes all three of .bash_profile, .bash_login and .profile are read
- Claims a new terminal tab always runs /etc/profile
- Thinks aliases defined in a parent shell are inherited by children
- Assumes editing .bashrc changes shells that are already running