Interactive vs Non-Interactive Shell
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/bgto 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
~/.bashrcby 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 bashin 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 bashas 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 |