Bash is a great coding language, which allows you to do complex things like Big Data Manipulation, or simply create sever or desktop management scripts.
The entry level skill required to use the Bash language is quite low, and one-liner scripts (an often used jargon, which indicates multiple commands executed at the command line, forming a mini-script), as well as regular scripts, can grow in complexity (and how well written they are) as the Bash developer learns more.
Learning to use special variables in Bash is one part of this learning curve. Whereas originally the special variables may look cryptic: $$, $?, $*, \$0, $1, etc.
, once you understand them and use them in your own scripts, things will soon become clearer and easier to remember.
In this tutorial you will learn:
- How to use special variables in Bash
- How to correctly quote variables, even special ones
- Examples using special variables from the command line and scripts
Software requirements and conventions used
Category | Requirements, Conventions or Software Version Used |
---|---|
System | Linux Distribution-independent |
Software | Bash command line, Linux based system |
Other | Any utility which is not included in the Bash shell by default can be installed using sudo apt-get install utility-name (or yum install for RedHat based systems) |
Conventions | # – requires linux-commands to be executed with root privileges either directly as a root user or by use of sudo command$ – requires linux-commands to be executed as a regular non-privileged user |
-
$$ – display the PID (Process Identifier)
In this example, we use the special variable
$$
to display the PID (Process Identifier) for our current program. This works a bit differently depending on whether you use this variable from the command line:$ echo $$ 316204 $ ps -ef | grep -E "$$|PID" UID PID PPID C STIME TTY TIME CMD roel 316204 62582 0 11:53 pts/2 00:00:00 bash roel 316499 316204 0 11:57 pts/2 00:00:00 ps -ef roel 316500 316204 0 11:57 pts/2 00:00:00 grep -E 316204|PID
Or from within a script. For example, let’s consider the following script
test.sh
:echo $$ ps -ef | grep -E "$$|PID"
Which, when we make it executable (
chmod +x test.sh
) and execute, produces:$ chmod +x test.sh $ ./test.sh 316820 UID PID PPID C STIME TTY TIME CMD roel 316820 316204 0 12:01 pts/2 00:00:00 bash roel 316821 316820 0 12:01 pts/2 00:00:00 ps -ef roel 316822 316820 0 12:01 pts/2 00:00:00 grep -E 316820|PID
The difference is in the PID produced! This may at first glance make conceptual sense, but let’s explain the main reason why the PID differs: we are using a different Bash shell. The first command executed was directly at the command line, and thus our special
$$
variable (which identifies the PID of the currently running program) produces the PID of the currently running bash shell (being 316204).In the second instance, we are running a script and each start of a script will always start a new Bash shell. The result is that our PID is the PID of the newly started Bash shell (316820). We can also confirm this by looking at the PPID (i.e. Parent PID, or the parent of the process identifier) – it is 316204 which matches our Bash shell from which we started the script, as seen in the first example (both the first and second example were executed in the same terminal on the same machine).
The
grep -E
command in our two examples allows us to capture the first line of the machine’s full process list (as obtained byps -ef
) by allowing extended regex support and grepping forPID
besides our PID (by using$$
). The|
is the extended regular expression separator which allows this dual capture.For more information on regular expressions, please see our Bash Regexps for Beginners with Examples and Advanced Bash Regex with Examples articles.
Also note that we have automated the PID capture by using
$$
in thegrep
command. This$$
variable never changes unless a new Bash shell / subshell is started, as we can see in the following example:$ echo $$ 316204 $ bash $ echo $$ 318023 $ echo $PPID 316204
The PID of our main Bash shell is still 316204 as before. Next, we start a new subshell and the PID of this new shell is 318023 when inspected. And, using the automatically-set (by Bash) variable
$PPID
we can confirm the PPID (Parent Process ID) of the secondary Bash shell/subshell as 316204, which matches our main shell. As you can see, in terms of process management and specifically the$$
variable, there is not much difference between starting a script and a new subshell.For more information on Bash process management, you may like to checkout our Bash Background Process Management and Process List Management and Automatic Process Termination articles.
-
$? – exit code
The
$?
variable tells us what the exit code was of the previous command. Knowing the exit code of an executed statement allows us to continue a script in two or more different directions. For example, if we started arm
command (to delete some files) from within a program, we may want to check if the process completed successfully.If the exit code is
0
, it generally (read: almost always) means that a process terminated successfully. If however the exit code is1
(or more) it often (though not always) means that the process terminated with an error or negative outcome, for example the file could not be deleted in our example. Let’s see how this works at the command line, remembering that the working of this variable from within a script is identical.$ touch this.exists $ rm this.exists $ echo $? 0 $ rm this.does.not.exist rm: cannot remove 'this.does.not.exist': No such file or directory $ echo $? 1
We first create a file
this.exists
by using thetouch
command.touch
simply creates a zero-size file without writing anything to it. Next we remove the file usingrm this.exists
and display the$?
exit code usingecho
. The result is 0 as the command succeeded as anticipated and seen by no error being returned.Next, we try and delete a file which does not exist and receive an error. When we check the exit code it is indeed
1
indicating some error occurred. We can check the value of this variable easily from the command line or from within a script by using anif [ $? -eq 0 ]; then
or similar conditional statement (terminated byfi
).To learn more about
if
based statements, please see Bash If Statements If Elif Else Then Fi. Combining$?
withif
statements is a common and powerful to automate various things in Bash. -
$1, $2, … $* – passing arguments
When we start a script at the Bash command line, we can pass arguments to the same. It is fully up to the script to handle the arguments passed to it. If the script for example does no handling of arguments at all (the default), then there is no consequence to either specifying or not specifying any, or many, variables to a script.
We can handle passed arguments by using the special variables
$1
,$2
,$*
etc. The first argument passed to the script will always be$1
, the second argument will always be$2
etc. One thing to watch out for is that if you introduce a space in a default configured Bash client, then Bash will interpret that space as a separator.If you are trying to pass some text like for example
this is an example
you would need to quote it properly like this:"this is an example";
in order for Bash to see that text as a single variable being passed.
The special
$*
variable is a shorthand for writing all variables into a single string. Let’s see how this works by defining a newtest2.sh
script as follows:echo "1: ${1}" echo "2: ${2}" echo "All: ${*}"
As a slight variation, we chose to define our variables here as
${1}
to${*}
instead of$1
to$*
. In fact, it would be a good idea to always quote variables this way. For more information, please have a look at our Correct Variable Parsing and Quoting In Bash article.When we execute the same, using either two or three arguments, we see:
$ chmod +x test2.sh $ ./test2.sh '1' '2' 1: 1 2: 2 All: 1 2 $ ./test2.sh '1' '2' '3' 1: 1 2: 2 All: 1 2 3
We can see how our first input to the script is being correctly recognized as
$1
etc. Also, we notice that the third argument is completely ignored by the script until reaching theecho "All: ${*}"
instruction which indeed shows all arguments as discussed earlier. Let’s now explore an incorrect input without quoting:$ ./test2.sh This is meant to be a single sentence. 1: This 2: is All: This is meant to be a single sentence. $ ./test2.sh "This is meant to be a single sentence." 1: This is meant to be a single sentence. 2: All: This is meant to be a single sentence.
Here it becomes clear how a space can be interpreted as a separator instead of an actual space, unless the text is properly quoted. In the first result, This is seen as the first argument, whereas in the second result, the whole sentence is seen as the first argument.
-
$0 – the command running
Having learned about
$1
, one could wonder what the\$0
special variable does. If you think about how a command is formed (command argument1 argument2
etc.), you may notice howcommand
comes before the first argument ($1
). Command, in a way, is thus – visually –\$0
, and this is exactly what the special\$0
variable contains; the command running.$ echo \$0 bash
As we can see, and as makes sense, at the command line, the currently running command is
bash
. If we add theecho \$0
command to a test scripttest3.sh
and execute the same, we get:$ ./test3.sh ./test3.sh $ ../workspace/test3.sh ../workspace/test3.sh
As now the currently running command is
./test3.sh
, exactly as executed from the command line. If we start the command using a longer path name like../workspace/test3.sh
then again this is repeated back via the special\$0
variable.
Conclusion
In this article, we explored the $$
, $?
, $1, $2, etc.
, $*
and \$0
variables, how they work and how you can use them either directly from the command line or from within scripts. There are a few other special variables, but these are the main special variables in Bash which I have used throughout many years of Bash coding. Enjoy!