Interactive vs Non-Interactive Shell

3 minute read

One of the most common sources of confusion for beginners: “Why do my aliases work in the terminal but not in my script?” The answer is interactive vs non-interactive shell mode.


What Changes Between Modes

Interactive Shell

A shell is interactive when it is attached to a terminal and waiting for you to type commands.

What an interactive shell does:

  • Displays a prompt ($, %, or custom PS1)
  • Loads ~/.bashrc (on most Linux distros)
  • Enables job control (Ctrl+Z to suspend, fg/bg to control jobs)
  • Expands aliases (e.g., ll → ls -la)
  • Enables command history
  • Enables readline (arrow keys, Ctrl+R search)

Non-Interactive Shell (Script Mode)

A shell is non-interactive when it is reading commands from a file or pipe, not from a user.

What a non-interactive shell does NOT do:

  • Does not display a prompt
  • Does not load ~/.bashrc by default
  • Does not expand aliases (aliases are interactive-only)
  • Does not enable job control
  • Still expands variables, does glob matching, runs pipes
#!/usr/bin/env bash
# This is non-interactive — no prompt, no ~/.bashrc loaded
ll          # ERROR: ll alias not found in non-interactive mode
ls -la      # Use the full command instead

Detecting the Shell Mode in Scripts

You can check if the current shell is interactive:

# Method 1: Check $- (current shell options)
if [[ $- == *i* ]]; then
  echo "Shell is interactive"
else
  echo "Shell is non-interactive (script mode)"
fi
# Method 2: Check if stdin is a terminal
if [[ -t 0 ]]; then
  echo "Stdin is a terminal — interactive"
else
  echo "Stdin is not a terminal — script mode"
fi

The Shebang Line

Every script should start with a shebang — a special first line that tells the OS which interpreter to use.

#!/usr/bin/env bash

Breaking It Down

#!              ← "shebang" — tells the kernel: "use this interpreter"
/usr/bin/env    ← the env command — searches $PATH for the next word
bash            ← the interpreter to find and use

Why env bash instead of /bin/bash?

# Hard-coded path — only works if bash is at exactly /bin/bash
#!/bin/bash

# Portable path — works wherever bash is installed
#!/usr/bin/env bash

On macOS with Homebrew Bash installed at /opt/homebrew/bin/bash, #!/usr/bin/env bash will find the modern version. #!/bin/bash would use the old Bash 3.2 in /bin/bash.

Best practice: Always use #!/usr/bin/env bash in your scripts.


Why Aliases Don’t Work in Scripts

# ~/.bashrc
alias ll='ls -la --color=auto'

# myscript.sh
#!/usr/bin/env bash
ll /etc   # ← This will FAIL with "command not found: ll"

Why? Scripts run in a non-interactive shell. The non-interactive shell does not source ~/.bashrc, so ll is never defined.

Solution 1: Use the full command in scripts:

ls -la /etc

Solution 2: (rare, not recommended) Enable alias expansion explicitly:

#!/usr/bin/env bash
shopt -s expand_aliases
source ~/.bashrc
ll /etc   # Now works, but this is fragile — avoid it

Why .bashrc Is Not Loaded in Scripts

Script execution path:
1. Kernel sees shebang: #!/usr/bin/env bash
2. Kernel launches bash as a non-interactive shell
3. Bash does NOT source ~/.bashrc (non-interactive = no user config)
4. Script runs with a clean environment (plus inherited env vars from parent)

This is actually a feature, not a bug. Scripts should not depend on the user’s personal configuration. A script that works on your laptop because of something in your .bashrc might fail on a CI server or another user’s machine.

Write scripts to be self-contained. Never rely on aliases or functions defined in .bashrc.


Environment Variables ARE Inherited

While .bashrc is not loaded, environment variables (exported variables) from the parent shell are inherited:

# In your terminal:
export MY_API_KEY="abc123"
bash myscript.sh

# In myscript.sh:
echo "$MY_API_KEY"   # ← This works — inherited from parent shell
echo "$MY_ALIAS"     # ← This fails — aliases are not inherited

This is how CI/CD systems pass credentials and configuration to scripts — through environment variables.


Quick Reference

Feature Interactive Non-Interactive
Prompt (PS1) ✅ Shown ❌ Not shown
~/.bashrc loaded ✅ Yes ❌ No
Aliases available ✅ Yes ❌ No
Job control (Ctrl+Z, fg, bg) ✅ Yes ❌ No
Command history ✅ Yes ❌ No
Environment variables ✅ Yes ✅ Inherited from parent
Variable expansion ✅ Yes ✅ Yes
Pipelines and redirection ✅ Yes ✅ Yes

Key Takeaways

  • Interactive: you type commands; shell loads .bashrc, enables aliases, shows prompt
  • Non-interactive: script runs; shell does NOT load .bashrc; no aliases, no prompt
  • Always use #!/usr/bin/env bash as your shebang line
  • Never rely on aliases in scripts — use the full command
  • Environment variables (exported) ARE passed to scripts from the parent shell

← Back to The Terminal Emulator Next → Practice Exercises

Updated: