Threat Picture

Latest Cybersecurity News
What the $!$*$@ … Bash Shell Variables

What the $!$*$@ … Bash Shell Variables

Follow one script to see how Bash reports arguments, exit status, process IDs, and the previous command’s last argument.
By Charles Joseph · Updated
Share
Share
Copy Link

Bash keeps a set of special variables that describe the script you're running, from the arguments it received to its own process ID and name.

Each one is a dollar sign plus a single character, and the fastest way to learn them is to print them all at once.

The short script below prints all nine.

The Script and Its Two Arguments

The script prints each variable next to its name.

A backslash before each label's dollar sign makes Bash print the label as plain text. The variable after it has no backslash, so Bash swaps in its value.

First, though, the script starts a background job.

seq generates a list of numbers, sort -nr sorts them from highest to lowest, and > /dev/null throws the output away.

The & at the end runs that chain in the background, which gives $! a process ID to report.

#!/bin/bash

# Run a background job so $! contains its process ID.
# seq prints a range of numbers: seq 3 prints 1, 2, 3 on separate lines.
seq 1000000 | sort -nr > /dev/null &

echo "All arguments (\$*):       $*"
echo "All arguments (\$@):       $@"
echo "Argument count (\$#):      $#"
echo "Last exit status (\$?):    $?"
echo "Shell options (\$-):       $-"
echo "Shell process ID (\$\$):   $$"
echo "Background job PID (\$!):  $!"
echo "Script name (\$0):         $0"
echo "Previous last arg (\$_):   $_"

Save it as shell-variables.sh, then run it with two arguments:

bash ./shell-variables.sh "I love" "handsonkeyboard.com"

The quotes matter. "I love" is one argument even though it has two words, and "handsonkeyboard.com" is the second.

Sponsored

What the Script Prints

Your process IDs will differ, and your shell option flags might too.

All arguments ($*):       I love handsonkeyboard.com
All arguments ($@):       I love handsonkeyboard.com
Argument count ($#):      2
Last exit status ($?):    0
Shell options ($-):       hB
Shell process ID ($$):   2370055
Background job PID ($!):  2370057
Script name ($0):         ./shell-variables.sh
Previous last arg ($_):   Script name ($0):         ./shell-variables.sh

What Each Variable Holds

VariableWhat it holdsValue in this run
$*All the arguments joined into one string, separated by the first character of IFS, Bash's internal field separator, which is normally a space.I love handsonkeyboard.com
$@All the arguments, each one kept separate.I love handsonkeyboard.com
$#How many arguments arrived.2
$?The exit status of the last command that ran in the foreground. Background jobs don't count.0
$-The single-letter option flags that are switched on. h means Bash remembers where it found each command, and B means brace expansion is on.hB
$$The shell's own process ID.2370055
$!The process ID of the most recent background job, here the sorting job at the top of the script.2370057
$0The script name the way you typed it../shell-variables.sh
$_The last argument of the previous foreground command, after Bash fills in its variables.Script name ($0): ./shell-variables.sh

The process IDs belong to this one run, not to the variables.

Three Lines That Need a Closer Look

Sponsored

Why the Two Argument Lines Match

The argument count says two, yet the two lines above it show three words.

"$*" joins the arguments into one string, while "$@" keeps them separate: I love stays one piece and handsonkeyboard.com another.

But echo prints its arguments with a space between each one, so on the screen you can't tell where one ends and the next begins.

Use "$@" when you pass the arguments on to another command and need them kept in one piece.

Which Command the Exit Status Describes

$? reports the echo right before it, the one that prints the argument count. It succeeded, so the line shows 0.

Every new foreground command replaces that value, so check $? right after the command you care about.

Why the Last Line Repeats

The previous command is the echo that prints the script name, and its whole quoted text is one argument.

So $_ holds that entire line, label and all, not just the filename.

If a value surprises you, trace it back to its source: the arguments, the shell, or the command right before it.

For the formal definitions and quoting rules, the GNU Bash special parameters reference covers every symbol from $* through $0.

QUOTE:

"Amateurs hack systems, professionals hack people."