Bash Special Variables Reference

Every Bash special variable ($?, $$, $!, $@, etc.) with meaning and scope.

Searchable reference for Bash special and automatic variables — $?, $$, $!, $0, $#, $@, $*, $-, $_, $BASHPID, $RANDOM, $LINENO, $IFS, $PIPESTATUS — each with its meaning, scope, and a usage example. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What is the difference between $@ and $*?

Both expand to all positional parameters. Unquoted they behave the same, but quoted they differ: "$@" expands to each argument as a separate quoted word, preserving spaces, while "$*" joins them into one word separated by the first character of IFS. In loops always use "$@".

Bash exposes a set of special variables the shell maintains for you — exit status, process IDs, positional parameters, and more. Misreading them, or forgetting that $? is overwritten immediately, causes subtle scripting bugs. This tool is a searchable reference of every special and automatic variable with its meaning, scope, and a usage example.

How it works

These variables fall into a few families:

  • Status & control$? (last exit code), $- (current shell flags), PIPESTATUS (per-command status of the last pipeline).
  • Process IDs$$ (shell PID), $BASHPID (current process PID), $! (PID of the last background job), $PPID (parent PID).
  • Positional parameters$0 (script name), $1$9, ${10}, $# (count), $@ and $* (all args), $_ (last arg of previous command).
  • Shell internals$IFS (field separator), $RANDOM, $SECONDS, $LINENO, $FUNCNAME, $BASH_SOURCE.

Worked example

#!/usr/bin/env bash
process() {
  grep -q "$1" "$2"          # may succeed or fail
  local status=$?            # capture immediately
  if (( status != 0 )); then
    echo "not found (exit $status)" >&2
  fi
}

for arg in "$@"; do          # quoted "$@" keeps each arg intact
  process "$arg" config.txt
done

false | true
echo "${PIPESTATUS[0]}"      # 1 — the failing 'false', not the pipeline's 0

Notes

  • Always quote "$@" in loops and when forwarding arguments; unquoted $@ re-splits on whitespace and breaks filenames with spaces.
  • $? is clobbered by the next command — including the test in an if. Save it to a local variable the moment you need it.
  • $$ stays constant across subshells (it is the main shell’s PID); reach for $BASHPID when you genuinely need the current subshell’s PID.
  • $RANDOM returns a value 0–32767 and is not cryptographically secure; use /dev/urandom for security-sensitive randomness.

Commonly misused variables

$@ versus $* in practice

The distinction between "$@" and "$*" only matters when they are double-quoted, but that is exactly when you use them — inside scripts where argument safety is important:

# Set IFS to comma to demonstrate $*
IFS=,
args=("first arg" "second arg")
set -- "${args[@]}"

echo "$@"   # prints: first arg second arg   (two arguments, each preserved)
echo "$*"   # prints: first arg,second arg   (joined on IFS character)

Use "$@" in virtually every case. The only time "$*" is useful is when you explicitly want to join all arguments into one string with a custom separator, and even then it is usually clearer to use printf '%s\n' "$@" | paste -sd, instead.

PIPESTATUS timing

PIPESTATUS is only valid immediately after a pipeline. The next command — including a variable assignment — overwrites it. Capture the whole array at once:

cmd1 | cmd2 | cmd3
statuses=("${PIPESTATUS[@]}")   # capture before anything else runs
echo "cmd1: ${statuses[0]}, cmd2: ${statuses[1]}, cmd3: ${statuses[2]}"

$0 in scripts versus at the terminal

$0 is the name of the running script when inside a script, but at an interactive prompt it reports the shell itself (bash or -bash for a login shell). If your script relies on $0 to find its own path, use $(realpath "$0") or ${BASH_SOURCE[0]} — the latter works correctly even when the script is sourced rather than executed.

$LINENO in functions

$LINENO gives the current line number, but when used inside a function it reports the line within the function, not the overall script. Combined with $BASH_SOURCE and $FUNCNAME, it can build precise error messages: "Error at ${BASH_SOURCE[0]}:${LINENO} in ${FUNCNAME[0]}".