Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Bash & the POSIX Shell  Module 13 – The interactive shell

Startup files, PATH and login shells

Which startup files Bash reads for login, interactive and non-interactive shells, where to set PATH, and why a script fails in cron or CI.

  • Intermediate
  • 5 minutes
  • Examples run with Bash 5.3.20, dash 0.5.13.5, shellcheck 0.11.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 5 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain which startup files Bash reads for a login, an interactive and a non-interactive shell
  • Set PATH and environment variables in the startup file that is actually read
  • Diagnose a script that works in your terminal but fails in cron or CI

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain which startup files Bash reads for a login, an interactive and a non-interactive shell
  • Set PATH and environment variables in the startup file that is actually read
  • Diagnose a script that works in your terminal but fails in cron or CI

Before you start

On this page

You add a line to ~/.bashrc to put ~/bin on your PATH, open a new terminal, and it works. Then the same command runs in a cron job and fails with “command not found”, exit status 127, because a cron job never reads ~/.bashrc at all.

Bash reads a different set of startup files depending on how it was started: as a login shell, as an interactive shell, or as a plain script. Here you learn which file each kind reads, where to set PATH so every shell sees it, and how to fix the classic “works in my terminal, fails in cron” bug.

Which file runs: login, interactive, or a script

A login shell: The shell your account starts when you log in, recorded in the SHELL variable. It need not be the shell that is running a given script. More in “Shells, terminals and which Bash you have” is the one your account starts when you log in. An interactive shell: A shell that reads commands you type at a prompt, rather than from a script, with history, aliases and line editing turned on. An interactive Bash that is not a login shell reads ~/.bashrc when it starts. is any shell that reads commands you type. A non-interactive shell runs a script and reads nothing you type. Each reads different startup file: A file Bash reads when it starts, such as ~/.bash_profile for a login shell or ~/.bashrc for an interactive shell, used to set up the environment.. The session makes three files in a sandboxed home that announce themselves, then starts each kind of shell:

Which startup files each shell reads Python · startup_trace_session.py
from session import run

# Which startup files a login, an interactive non-login and a non-interactive Bash reads. Three files in a sandboxed
# HOME announce themselves; the login shell reads .bash_profile, the interactive shell reads .bashrc, the plain script
# reads neither until BASH_ENV names a file.
run("""
printf '%s\\n' 'echo "ran: .bash_profile"' > ~/.bash_profile
printf '%s\\n' 'echo "ran: .bashrc"' > ~/.bashrc
printf '%s\\n' 'echo "ran: .profile"' > ~/.profile
bash --login -c 'echo "(login shell ran its body)"'
bash -i -c 'echo "(interactive shell ran its body)"' 2>/dev/null
printf '%s\\n' 'echo "(plain script ran its body)"' > show.sh
bash show.sh
BASH_ENV=~/.bashrc bash show.sh
""")

Output

$ printf '%s\n' 'echo "ran: .bash_profile"' > ~/.bash_profile
$ printf '%s\n' 'echo "ran: .bashrc"' > ~/.bashrc
$ printf '%s\n' 'echo "ran: .profile"' > ~/.profile
$ bash --login -c 'echo "(login shell ran its body)"'
ran: .bash_profile
(login shell ran its body)
$ bash -i -c 'echo "(interactive shell ran its body)"' 2>/dev/null
ran: .bashrc
(interactive shell ran its body)
$ printf '%s\n' 'echo "(plain script ran its body)"' > show.sh
$ bash show.sh
(plain script ran its body)
$ BASH_ENV=~/.bashrc bash show.sh
ran: .bashrc
(plain script ran its body)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 startup_trace_session.py

The login shell (bash --login) printed ran: .bash_profile before its body: a login shell reads /etc/profile, then the first file that exists of ~/.bash_profile, ~/.bash_login and ~/.profile. The interactive shell (bash -i) printed ran: .bashrc: an interactive non-login shell reads ~/.bashrc only. The plain script (bash show.sh) read none of them. Only when BASH_ENV named a file did the script read it first.

Interactive · step by step Which startup file each kind of shell reads Step through the three shell kinds and watch which file each one reads before it runs your commands.
Which startup file each kind of shell reads, step 5 of 5. Showing Login shell, Interactive shell, Plain script, ~/.bash_profile (first that exists wins), ~/.bashrc, file in $BASH_ENV. In focus: ~/.bash_profile, ~/.bashrc.readsreadsif setLogin shellInteractive shellPlain script~/.bash_profilefirst that exists wins~/.bashrcfile in $BASH_ENV

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. A login shell. A login shell (bash --login, or the shell you get when you log in) reads /etc/profile, then the first file that exists of ~/.bash_profile, ~/.bash_login and ~/.profile. In the session it ran ~/.bash_profile.
  2. An interactive non-login shell. A shell you open in an already-logged-in terminal is interactive but not a login shell. It reads ~/.bashrc only. In the session it printed "ran: .bashrc".
  3. A non-interactive script. Running bash show.sh starts a non-interactive shell. It reads none of these files, so a setting that lives only in ~/.bashrc is not there.
  4. Unless BASH_ENV names a file. A non-interactive Bash reads the file named in $BASH_ENV, if any. BASH_ENV=~/.bashrc bash show.sh ran ~/.bashrc first. (Bash started by sshd for ssh host cmd reads ~/.bashrc as a special case.)
  5. The usual pattern. To give both login and interactive shells the same settings, a short ~/.bash_profile sources ~/.bashrc, so PATH and the rest are set once and read by both.

How Bash started File it reads first
Login shell (bash -l, the login terminal) /etc/profile, then first of ~/.bash_profile, ~/.bash_login, ~/.profile
Interactive, not a login shell ~/.bashrc
Non-interactive (bash script.sh) nothing, unless $BASH_ENV names a file

One platform note: macOS has used zsh as the default login shell since macOS 10.15, so Bash reads its startup files only when you run bash yourself. The files and the rules are the same; which shell greets you at login is what differs.

Key idea

A login shell reads a profile file, an interactive shell reads ~/.bashrc, and a plain script reads neither unless $BASH_ENV points at a file.

Checkpoint

Check: Which file each shell reads

  1. Question 1 of 2 A login shell starts. Which file does it read first of these three?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: ~/.bash_profile

    In the session bash --login printed "ran: .bash_profile" before its body.

  2. Question 2 of 2 You run bash show.sh. A function defined only in ~/.bashrc is not found. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A non-interactive script reads none of the startup files

    In the session bash show.sh printed only its body; BASH_ENV=~/.bashrc made it read ~/.bashrc first.

Put PATH and settings in the file that is read

Because the two kinds of interactive shell read different files, a setting you put in ~/.bash_profile is missing from the terminals you open later, and a setting in ~/.bashrc is missing from a login shell. The common cure is a short ~/.bash_profile whose only job is [[ -f ~/.bashrc ]] && source ~/.bashrc, so both read the same file.

Put PATH in ~/.bashrc (sourced by both) and you are done. PATH is a search path: PATH: the colon-separated list of directories Bash searches left to right for a command. The first match is the program that runs.: a colon-separated list of directories that Bash looks through, left to right, for the program you name. The first match wins, so where you add a directory decides which program runs:

PATH order, type -a and removing a duplicate Python · path_session.py
from session import run

# PATH order decides which program runs. Two directories hold a program of the same name; the one earlier in PATH
# wins, type -a shows every match in order, and a duplicate entry is harmless but easy to remove.
run("""
mkdir -p ~/bin ~/extra
printf '#!/bin/sh\\necho "from bin"\\n' > ~/bin/greet
printf '#!/bin/sh\\necho "from extra"\\n' > ~/extra/greet
chmod +x ~/bin/greet ~/extra/greet
PATH="$HOME/extra:$PATH"
PATH="$HOME/bin:$PATH"
type -a greet
greet
PATH="$HOME/bin:$PATH"
printf '%s\\n' "$PATH" | tr ':' '\\n' | grep -Fxc "$HOME/bin"
PATH=$(printf '%s' "$PATH" | awk -v RS=: '!seen[$0]++' | paste -sd: -)
printf '%s\\n' "$PATH" | tr ':' '\\n' | grep -Fxc "$HOME/bin"
""")

Output

$ mkdir -p ~/bin ~/extra
$ printf '#!/bin/sh\necho "from bin"\n' > ~/bin/greet
$ printf '#!/bin/sh\necho "from extra"\n' > ~/extra/greet
$ chmod +x ~/bin/greet ~/extra/greet
$ PATH="$HOME/extra:$PATH"
$ PATH="$HOME/bin:$PATH"
$ type -a greet
greet is ~/bin/greet
greet is ~/extra/greet
$ greet
from bin
$ PATH="$HOME/bin:$PATH"
$ printf '%s\n' "$PATH" | tr ':' '\n' | grep -Fxc "$HOME/bin"
2
$ PATH=$(printf '%s' "$PATH" | awk -v RS=: '!seen[$0]++' | paste -sd: -)
$ printf '%s\n' "$PATH" | tr ':' '\n' | grep -Fxc "$HOME/bin"
1

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 path_session.py

Two directories each hold a program called greet. After PATH="$HOME/bin:$PATH" put ~/bin first, type -a greet listed both matches in search order (~/bin/greet before ~/extra/greet) and running greet printed from bin, the first one. Prepending the same directory twice left a harmless duplicate: the count of ~/bin entries was 2, and the awk one-liner that keeps the first copy of each entry brought it back to 1.

Prepend with the old PATH on the end

Write PATH="$HOME/bin:$PATH", never PATH="$HOME/bin". The second form throws away every system directory, so ls, grep and the rest stop being found. Never put the current directory (. or an empty entry) on PATH.

Checkpoint

Check: PATH order and the right file

  1. Question 1 of 2 type -a greet lists ~/bin/greet before ~/extra/greet. Running greet prints which line?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: from bin

    The session printed "from bin" after type -a listed ~/bin/greet first.

  2. Question 2 of 2 A script sets PATH="$HOME/bin" (with no :$PATH). What breaks?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Every system command such as ls and grep stops being found

    Prepend with PATH="$HOME/bin:$PATH" so the system directories stay on the path.

Why it works in your terminal but fails in cron or CI

Your terminal is a rich place: a login shell and then ~/.bashrc have set PATH, exported variables and defined functions. A cron job or a CI step starts a non-interactive shell with almost none of that. cron runs commands with /bin/sh and a small environment, taking HOME and LOGNAME from the password file; CI runners start a fresh shell per step. The session uses env -i, which starts a command with an empty environment, to reproduce the failure:

Reproducing the cron and CI environment with env -i Python · cron_env_session.py
from session import run

# A terminal carries a rich environment; cron and CI start a non-interactive shell with almost nothing. env -i mimics
# that empty environment: an exported variable is gone, and a program in ~/bin is not found until the script sets PATH.
run("""
export GREETING=namaste
mkdir -p ~/bin
printf '#!/bin/sh\\necho "backup done"\\n' > ~/bin/backup
chmod +x ~/bin/backup
PATH="$HOME/bin:$PATH"
backup
!env -i PATH=/usr/bin:/bin bash --noprofile --norc -c 'echo "GREETING=[$GREETING]"; backup'
env -i HOME="$HOME" PATH=/usr/bin:/bin bash --noprofile --norc -c 'PATH="$HOME/bin:$PATH"; backup'
""")

Output

$ export GREETING=namaste
$ mkdir -p ~/bin
$ printf '#!/bin/sh\necho "backup done"\n' > ~/bin/backup
$ chmod +x ~/bin/backup
$ PATH="$HOME/bin:$PATH"
$ backup
backup done
$ env -i PATH=/usr/bin:/bin bash --noprofile --norc -c 'echo "GREETING=[$GREETING]"; backup'
GREETING=[]
bash: backup: command not found
[exit status 127]
$ env -i HOME="$HOME" PATH=/usr/bin:/bin bash --noprofile --norc -c 'PATH="$HOME/bin:$PATH"; backup'
backup done

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cron_env_session.py

In the terminal, with ~/bin on PATH, backup printed backup done. Under env -i the exported GREETING was gone (GREETING=[]) and backup failed with “command not found”, status 127, because the small PATH did not include ~/bin. Setting PATH inside the command fixed it. That is the whole bug: the job does not read your ~/.bashrc, so whatever it needs must be set in the job itself.

Relying on your interactive setup in a job

Wrong: a cron line 30 2 * * * backup that assumes ~/bin is on PATH. Right: set a known PATH at the top of the script (PATH=/usr/local/bin:/usr/bin:/bin) or call the program by its absolute path, and never depend on ~/.bashrc having run.

The rule is the same for both cron and CI: treat the job as a shell that starts from nothing.

Key idea

A job reads none of your interactive startup files, so set PATH and any variable it needs inside the script or the crontab, or call programs by their absolute path.

Checkpoint

Check: Cron and CI environments

  1. One question A command runs in your terminal but a cron job reports "command not found" (status 127). The most likely cause is:

    Choose one answer.

    Show the answer: One question

    Answer: cron starts a shell that does not read ~/.bashrc, so PATH is missing ~/bin

    Under env -i the session's backup failed with "command not found" until PATH included ~/bin.

Practise: startup files, PATH and jobs

Each question shows a shell started a particular way, or a PATH change, like the ones in the sessions. Decide what happens, then check your answer against the recorded output:

Practice that adapts to you

Practise this lesson

7 questions about 3 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 You open a new terminal tab in an already-logged-in session. Which file does that shell read?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: ~/.bashrc

    The session's bash -i printed "ran: .bashrc".

  2. Question 2 of 7 How do you make a non-interactive bash script.sh read a file of settings first?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: Set BASH_ENV to that file before running the script

    BASH_ENV=~/.bashrc bash show.sh made the script run ~/.bashrc before its body.

  3. Question 3 of 7 Why do so many setups put only "[[ -f ~/.bashrc ]] && source ~/.bashrc" in ~/.bash_profile?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: So a login shell and an interactive shell both end up reading ~/.bashrc

    The login shell reads the profile, the profile sources ~/.bashrc, so settings live in one file.

  4. Question 4 of 7 Which command shows every place on PATH where a command named greet is found, in search order?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: type -a greet

    The session's type -a greet listed ~/bin/greet then ~/extra/greet.

  5. Question 5 of 7 PATH holds ~/bin twice. After piping PATH through awk -v RS=: '!seen[$0]++', how many times does ~/bin appear?

    Type a number.

    Show the answer: Question 5 of 7

    Answer: 1 entries

    The awk program keeps the first copy of each entry, so the duplicate count fell from 2 to 1 in the session.

  6. Question 6 of 7 A cron job needs a tool in ~/bin. What is the most reliable fix?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: Set PATH at the top of the script, or call the tool by its absolute path

    In the session, setting PATH inside the env -i command made backup run.

  7. Question 7 of 7 Why does env -i bash -c reproduce a cron or CI failure well?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: env -i starts the command with an empty environment, like the small one cron and CI give

    Under env -i the exported GREETING was gone and PATH no longer held ~/bin.

Interview questions

Warm-up (fresher to 2–5 years): which startup files does Bash read for a login shell, an interactive shell and a script?

A login shell reads /etc/profile and then the first file that exists of ~/.bash_profile, ~/.bash_login and ~/.profile. An interactive shell that is not a login shell reads ~/.bashrc. A non-interactive shell that runs a script reads none of these, except the file named in $BASH_ENV if that variable is set (and Bash started by sshd for ssh host cmd reads ~/.bashrc as a special case). The usual arrangement is a one-line ~/.bash_profile that sources ~/.bashrc, so a login shell and an interactive shell share the same settings, and PATH lives in ~/.bashrc.

Two harder questions follow, about a PATH that works by hand but not in cron, and a tool that is found in one terminal and not another. Decide what you would check first, then open each first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A backup script runs perfectly when you type it, but the same script in a cron job fails with "backup: command not found" and exit status 127. Explain the cause and two ways to fix it.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A developer can run a tool in the terminal they log in to, but a terminal they open inside a running desktop session reports "command not found". Both are Bash. What is the likely cause and the fix?

    Model answer, Pro. First step and follow-ups

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which startup files does a Bash login shell read?

    /etc/profile first; then it looks for ~/.bash_profile, ~/.bash_login and ~/.profile, in that order, and reads only the first one it finds.

  2. Which startup file does an interactive non-login shell read, and which does a script read?

    An interactive shell that is not a login shell reads ~/.bashrc. A shell running a script reads neither, unless BASH_ENV names a file for it to read.

  3. How do login and non-login interactive shells share one set of settings?

    A one-line ~/.bash_profile that sources ~/.bashrc, so the settings live in one file that both kinds of shell end up reading.

  4. How does Bash search PATH, and how should a folder be added to it?

    From left to right, and the first match wins. Prepend with PATH="$dir:$PATH" so the system folders stay; type -a NAME shows every match in order.

  5. Why does a command that works in your terminal fail in cron or CI?

    Cron and CI run the job in a non-interactive shell, and such a shell skips your startup files, so your PATH and variables are missing. Set them inside the job, or call programs by their absolute path.

Key takeaways

  • A login shell reads a profile file, an interactive non-login shell reads ~/.bashrc, and a plain script reads neither unless $BASH_ENV names a file.
  • A one-line ~/.bash_profile that sources ~/.bashrc lets both kinds of interactive shell share one set of settings.
  • PATH is searched left to right and the first match wins; prepend with PATH="$dir:$PATH" so the system directories stay, and type -a shows every match in order.
  • Cron and CI start a non-interactive shell that reads none of your startup files, so set PATH and any variable the job needs inside the job itself.

Terms in this lesson

interactive shell
A shell that reads commands you type at a prompt, rather than from a script, with history, aliases and line editing turned on. An interactive Bash that is not a login shell reads ~/.bashrc when it starts.
search path
PATH: the colon-separated list of directories Bash searches left to right for a command. The first match is the program that runs.
startup file
A file Bash reads when it starts, such as ~/.bash_profile for a login shell or ~/.bashrc for an interactive shell, used to set up the environment.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.