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
Special Bash Variables with examples
Special Bash Variables with examples

Software requirements and conventions used

Software Requirements and Linux Command Line Conventions
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
  1. $$ - 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 by ps -ef) by allowing extended regex support and grepping for PID 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 the grep 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.


    SUBSCRIBE NEWSLETTER & RSS
    Subscribe to RSS and NEWSLETTER and receive latest Linux news, jobs, career advice and tutorials.


  2. $? - 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 a rm 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 is 1 (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 the touch command. touch simply creates a zero-size file without writing anything to it. Next we remove the file using rm this.exists and display the $? exit code using echo. 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 an if [ $? -eq 0 ]; then or similar conditional statement (terminated by fi).

    To learn more about if based statements, please see Bash If Statements If Elif Else Then Fi. Combining $? with if statements is a common and powerful to automate various things in Bash.

  3. $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 , the second argument will always be 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 new test2.sh script as follows:

    echo "1: "
    echo "2: "
    echo "All: ${*}"
    

    As a slight variation, we chose to define our variables here as to ${*} instead of 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 etc. Also, we notice that the third argument is completely ignored by the script until reaching the echo "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.



  4. $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 how command 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 the echo $0 command to a test script test3.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!

FIND LATEST LINUX JOBS on LinuxCareers.com
Submit your RESUME, create a JOB ALERT or subscribe to RSS feed.
LINUX CAREER NEWSLETTER
Subscribe to NEWSLETTER and receive latest news, jobs, career advice and tutorials.
DO YOU NEED ADDITIONAL HELP?
Get extra help by visiting our LINUX FORUM or simply use comments below.


Comments and Discussions