Shells, startup files and running scripts
Interactive versus script shells, bash versus sh, shebangs, executable bits, startup files, sourcing versus executing, and the shell option set.
Which shell is running
A shell is a program that reads commands and runs them. The same script behaves differently depending on which shell reads it, so the first habit worth building is asking what is actually executing your code.
echo "$0" # name of the running shell or script
echo "$BASH_VERSION" # set only under bash
ps -p $$ -o comm= # the executable behind this shell
ls -l /bin/sh # on Debian/Ubuntu this is usually a symlink to dash
bash --version
sh --version 2>/dev/null || echo "sh is not bash"| Kind of shell | Started by | Typical use |
|---|---|---|
| Interactive login | ssh, a TTY login, bash -l | Your terminal session; reads profile files once |
| Interactive non-login | A new terminal tab, bash | Reads ~/.bashrc |
| Non-interactive | A script, cron, CI, bash -c | Reads no startup file unless BASH_ENV is set |
shis a name, not a shell. On most Linux distributions it isdash; on macOS it is bash in POSIX mode.- Running a script with
shsilently discards anything bash-specific:[[ ]], arrays,localin some builds. - An interactive shell keeps history and prints prompts; a script shell does neither, which is why interactive shortcuts fail inside scripts.
Shebangs, permissions and startup files
#!/usr/bin/env bash
# ^ the kernel runs the named interpreter, not your login shell
chmod +x deploy.sh
./deploy.sh # honoured: shebang decides the interpreter
bash deploy.sh # ignores the shebang, forces bash
sh deploy.sh # forces sh: bashisms break here
source deploy.sh # run in the CURRENT shell: variables and cd persist
. ./deploy.sh # identical, POSIX spelling| File | Read when |
|---|---|
/etc/profile | Login shells, before the user's own files |
~/.bash_profile | Login shells; bash uses the first of profile, login, bashrc-family it finds |
~/.bashrc | Interactive non-login shells |
~/.bash_logout | When a login shell exits |
$BASH_ENV | Non-interactive bash, but only if the variable is exported |
# make a login shell pick up interactive settings
[[ -f ~/.bashrc ]] && . ~/.bashrc
# and keep interactive-only code out of scripts
case $- in
*i*) ;;
*) return ;; # not interactive: stop here
esac💡
A script run by cron or CI reads none of your startup files, so
PATH, aliases and functions you rely on interactively simply are not there. Set the environment explicitly inside the script — that is the whole reason cron scripts "work in the terminal" and fail at 3am.The shell option set
set -o noclobber # > refuses to overwrite an existing file
set -o nounset # unset variable is an error
set -o pipefail # a pipeline fails if any stage fails
set +o noclobber # turn one back off
shopt -s nullglob # a glob with no match expands to nothing
shopt -s globstar # ** matches across directories
shopt -u nullglob # undo
shopt -p # print every shopt setting as a command
bash -n script.sh # syntax check only, run nothing
bash -x script.sh # trace every command as it executessetcontrols POSIX behaviour,shoptthe bash extensions. Both print their current state when called with-oor no argument.nullglobis the fix for the classic bug wherefor f in *.logiterates once over the literal text*.log.- Options set interactively do not reach a script unless you export them with
export SHELLOPTS, which is rarely what you want — put them in the script instead. bash -nis the fastest check in a commit hook: it catches a missingfibefore anything runs.
FAQ
Should my shebang be bash or sh?
Use
#!/usr/bin/env bash if you use arrays, [[ ]] or local. Use #!/bin/sh only when you have verified the script against dash or busybox. Pick one deliberately and test on the target.Why does my script find a command that a plain shell cannot?
Because your interactive shell loaded
~/.bashrc and a cron job did not. Export the variables you need, or set PATH at the top of the script.Related
Portability and POSIX shell Files, redirection and here-documents
Last refreshed 2026-09-18.