Shell Startup Files and Login vs Non-Login Shells
I mapped Bash's startup file precedence so customizations apply predictably whether a shell is login, non-login, or interactive. Sourcing .bashrc from .bash_profile guaranteed one canonical place for aliases and environment, avoiding "works in one terminal but not another" bugs.
Objective & Context
Startup file confusion causes environment drift between SSH sessions, terminals, and cron. This lab clarifies which files run for which shell type and establishes a single source of truth, making the aliases-env customizations persistent.
Learning Objectives
- Distinguish login, non-login, interactive, and non-interactive shells.
- Explain which startup files each shell type reads, in order.
- Source .bashrc from .bash_profile so settings apply everywhere.
- Place aliases, functions, and PATH changes in the correct file.
- Verify behavior with bash -l, bash -i, and echo $- checks.
Environment & Prerequisites
Prerequisites
- Bash as your shell with a writable home directory.
- The aliases and environment variables lab (you will persist those settings).
- Know how to open a new login shell:
bash -lor SSH in fresh. - No packages needed; this lab edits dotfiles only.
Step-by-Step Execution
1. Source .bashrc from .bash_profile
echo '[ -f ~/.bashrc ] && . ~/.bashrc' >> ~/.bash_profileExpected output:
(line appended; verify with: tail -1 ~/.bash_profile)
[ -f ~/.bashrc ] && . ~/.bashrc
What this teaches: Login shells read .bash_profile but not .bashrc, so sourcing .bashrc from it unifies the two paths. The -f guard keeps it safe when .bashrc is absent.
2. Put customizations in .bashrc
echo "alias ll='ls -lAh'" >> ~/.bashrc && source ~/.bashrcExpected output:
(alias appended and active immediately)
$ type ll
ll is aliased to `ls -lAh'
What this teaches: .bashrc is the home for interactive customizations (aliases, functions, prompts); sourcing it after editing applies changes without opening a new shell.
3. Confirm a login shell picks it up
bash -lc 'type ll'Expected output:
ll is aliased to `ls -lAh'
What this teaches: bash -lc runs a login shell: if ll resolves, the .bash_profile to .bashrc chain works. This is the definitive test that graphical-terminal and SSH logins share your setup.
How It Works
Bash classifies each invocation along two axes: login versus non-login, and interactive versus non-interactive. A login shell (SSH session, console login, bash -l) reads /etc/profile then the first of ~/.bash_profile, ~/.bash_login, ~/.profile. A non-login interactive shell (a new terminal tab) reads ~/.bashrc only. Non-interactive shells read nothing unless BASH_ENV points at a file, with one documented exception: bash started by sshd to run a remote command reads ~/.bashrc even though it is non-interactive, which is why remote ssh commands sometimes see aliases that cron jobs do not.
This split exists for a reason: login shells set up the session once (PATH, environment), while every interactive subshell needs the interactive comforts (aliases, prompts, completion). The standard pattern, sourcing .bashrc from .bash_profile, merges the two so interactive settings exist in both contexts without duplication.
.profile is the POSIX fallback read when no bash-specific login file exists; keeping it minimal and bash-agnostic preserves compatibility with sh and dash. Debugging startup issues means tracing this order: echo markers or bash -x -l reveal exactly which files ran.
Validation & Testing
Open a non-login interactive shell and a login shell and confirm the same alias/environment exists in both. Pass criteria: customizations apply consistently across login and non-login shells with no duplication.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Aliases missing in new terminal tabs | They were defined in .bash_profile, which non-login shells skip | Move interactive settings to .bashrc and source it from .bash_profile |
| PATH duplicated many times | .bashrc prepends to PATH on every nested shell | Guard with a case check: case ":$PATH:" in *:$HOME/.local/bin:*) ;; *) PATH=...;; esac |
| Changes to .bashrc had no effect | The running shell never re-read the file | Run source ~/.bashrc, or open a new shell |
| Login is slow | A startup file runs something heavy (nvm, pyenv, network calls) | Profile with bash -x -l and lazy-load expensive initializers |
| .bash_profile ignored entirely | ~/.bash_login exists and takes precedence | Consolidate into one login file; Bash reads only the first match |
| Scripts behave differently than the terminal | Non-interactive shells skip .bashrc | Put script-needed setup in BASH_ENV or export it from the caller |
Advanced: Troubleshooting
- Works in terminal, not SSH: SSH starts a login shell; ensure .bash_profile sources .bashrc.
- Duplicated PATH: guard appends so re-sourcing doesn't stack entries.
- cron lacks env: cron uses a minimal shell; set variables explicitly in the job.
Key Results
- Established one canonical file for shell customization.
- Eliminated environment drift between login and non-login shells.
- Verified consistent behaviour across SSH and local terminals.
- Prevented PATH duplication on repeated sourcing.
Key Takeaways
- Login shells read .bash_profile; interactive non-login shells read .bashrc.
- Source .bashrc from .bash_profile to unify both paths.
- Interactive comforts go in .bashrc; session setup goes in the login file.
- Guard PATH modifications against duplication in nested shells.
- bash -lc 'type ll' is the definitive login-shell test.
Practice Challenges
Challenge 1: Trace the chain
Goal: add echo markers to each startup file, then open a login shell and a non-login shell and record which markers appear in each.
Hint: bash -l versus a new terminal tab.
Challenge 2: Idempotent PATH
Goal: rewrite your PATH setup so sourcing .bashrc ten times still leaves exactly one copy of each entry.
Hint: the case pattern on :$PATH: is the classic guard.