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 shellStarted byTypical use
Interactive loginssh, a TTY login, bash -lYour terminal session; reads profile files once
Interactive non-loginA new terminal tab, bashReads ~/.bashrc
Non-interactiveA script, cron, CI, bash -cReads no startup file unless BASH_ENV is set
  • sh is a name, not a shell. On most Linux distributions it is dash; on macOS it is bash in POSIX mode.
  • Running a script with sh silently discards anything bash-specific: [[ ]], arrays, local in 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
FileRead when
/etc/profileLogin shells, before the user's own files
~/.bash_profileLogin shells; bash uses the first of profile, login, bashrc-family it finds
~/.bashrcInteractive non-login shells
~/.bash_logoutWhen a login shell exits
$BASH_ENVNon-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 executes
  • set controls POSIX behaviour, shopt the bash extensions. Both print their current state when called with -o or no argument.
  • nullglob is the fix for the classic bug where for f in *.log iterates 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 -n is the fastest check in a commit hook: it catches a missing fi before 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.

Portability and POSIX shell Files, redirection and here-documents

Last refreshed 2026-09-18.