What the $!$*$@ … Bash Shell Variables
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.
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
| Variable | What it holds | Value 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 |
$0 | The 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
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."