3.7. Variables Revisited

Used properly, variables can add power and flexibility to scripts. This requires learning their subtleties and nuances.

Internal (builtin) variables

environmental variables affecting bash script behavior

$IFS

input field separator

This defaults to white space, but may be changed, for example, to parse a comma-separated data file.

$HOME

home directory of the user, usually /home/username (see Example 3-6)

$HOSTNAME

name assigned to the system, usually fetched at bootup from /etc/hosts (see Example 3-6)

$UID

user id number

current user's user identification number, as recorded in /etc/passwd

This is the current user's real id, even if she has temporarily assumed another identity through su (see Section 3.11). $UID is a readonly variable, not subject to change from the command line or within a script.

$EUID

"effective" user id number

identification number of whatever identity the current user has assumed, perhaps by means of su

$GROUPS

groups current user belongs to

This is a listing (array) of the group id numbers for current user, as recorded in /etc/passwd.

$PATH

path to binaries, usually /usr/bin/, /usr/X11R6/bin/, /usr/local/bin, etc.

When given a command, the shell automatically searches the directories listed in the path for the executable. The path is stored in the environmental variable, $PATH, a list of directories, separated by colons. Normally, the system stores the $PATH definition in /etc/profile and/or ~/.bashrc (see Section 3.23).

 bash$ echo $PATH
 /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

PATH=${PATH}:/opt/bin appends the /opt/bin directory to the current path. In a script, it may be expedient to temporarily add a directory to the path in this way. When the script exits, this restores the original $PATH (a child process, such as a script, may not change the environment of the parent process, the shell).

Note

The current "working directory", ./, is usually omitted from the $PATH as a security measure.

$PS1

This is the main prompt, seen at the command line.

$PS2

The secondary prompt, seen when additional input is expected. It displays as ">".

$PS3

The tertiary prompt, displayed in a select loop (see Example 3-37).

$PS4

The quartenary prompt, shown at the beginning of each line of output when invoking a script with the -x option. It displays as "+".

$PWD

working directory (directory you are in at the time)

   1 #!/bin/bash
   2 
   3 WRONG_DIRECTORY=33
   4 
   5 clear # Clear screen.
   6 
   7 TargetDirectory=/home/bozo/projects/GreatAmericanNovel
   8 
   9 cd $TargetDirectory
  10 echo "Deleting stale files in $TargetDirectory."
  11 
  12 if [ $PWD != $TargetDirectory ]  # Keep from wiping out wrong directory by accident.
  13 then
  14   echo "Wrong directory!"
  15   echo "In $PWD, rather than $TargetDirectory!"
  16   echo "Bailing out!"
  17   exit $WRONG_DIRECTORY
  18 fi  
  19 
  20 rm -rf *
  21 rm .[A-Za-z0-9]*    # Delete dotfiles.
  22 
  23 # Various other operations here, as necessary.
  24 
  25 echo
  26 echo "Done."
  27 echo "Old files deleted in $TargetDirectory."
  28 echo
  29 
  30 
  31 exit 0

$OLDPWD

old working directory (previous directory you were in)

$DIRSTACK

contents of the directory stack (affected by pushd and popd)

This builtin variable is the counterpart to the dirs command (see Section 3.9).

$PPID

the process id (pid) of the currently running process

This corresponds to the pidof command (see Section 3.11).

$MACHTYPE

machine type

Identifies the system hardware.

 bash$ echo $MACHTYPE
 i686-debian-linux-gnu
$HOSTTYPE

host type

Like $MACHTYPE above, identifies the system hardware.

 bash$ echo $HOSTTYPE
 i686
$OSTYPE

operating system type

 bash$ echo $OSTYPE
 linux-gnu
$EDITOR

the default editor invoked by a script, usually vi or emacs.

$IGNOREEOF

ignore EOF: how many end-of-files (control-D) the shell will ignore before logging out.

$TMOUT

If the $TMOUT environmental variable is set to a non-zero value time, then the shell prompt will time out after time seconds. This will cause a logout.

Note

Unfortunately, this works only while waiting for input at the shell prompt console or in an xterm. While it would be nice to speculate on the uses of this internal variable for timed input, for example in combination with read, $TMOUT does not work in that context and is virtually useless for shell scripting. (Reportedly the ksh version of a timed read does work).

Implementing timed input in a script is certainly possible, but hardly seems worth the effort. It requires setting up a timing loop to signal the script when it times out. Additionally, a signal handling routine is necessary to trap (see Example 3-100) the interrupt generated by the timing loop (whew!).

   1 #!/bin/bash
   2 
   3 # TMOUT=3            useless in a script
   4 
   5 TIMELIMIT=3  # Three seconds in this instance, may be set to different value.
   6 
   7 PrintAnswer()
   8 {
   9   if [ $answer = TIMEOUT ]
  10   then
  11     echo $answer
  12   else       # Don't want to mix up the two instances. 
  13     echo "Your favorite veggie is $answer"
  14     kill $!  # Kills no longer needed TimerOn function running in background.
  15              #   $! is PID of last job running in background.
  16   fi
  17 
  18 }  
  19 
  20 
  21 
  22 TimerOn()
  23 {
  24   sleep $TIMELIMIT && kill -s 14 $$ &
  25   # Waits 3 seconds, then sends sigalarm to script.
  26 }  
  27 
  28 Int14Vector()
  29 {
  30   answer="TIMEOUT"
  31   PrintAnswer
  32   exit 14
  33 }  
  34 
  35 trap Int14Vector 14
  36 # Timer interrupt - 14 - subverted for our purposes.
  37 
  38 echo "What is your favorite vegetable "
  39 TimerOn
  40 read answer
  41 PrintAnswer
  42 
  43 
  44 # Admittedly, this is a kludgy implementation of timed input,
  45 # but pretty much as good as can be done with Bash.
  46 # (Challenge to reader: come up with something better.)
  47 
  48 # If you need something a bit more elegant...
  49 # consider writing the application in C or C++,
  50 # using appropriate library functions, such as 'alarm' and 'setitimer'.
  51 
  52 exit 0

$SECONDS

The number of seconds the script has been running.

   1 #!/bin/bash
   2 
   3 ENDLESS_LOOP=1
   4 
   5 echo
   6 echo "Hit Control-C to exit this script."
   7 echo
   8 
   9 while [ $ENDLESS_LOOP ]
  10 do
  11   if [ $SECONDS -eq 1 ]
  12   then
  13     units=second
  14   else  
  15     units=seconds
  16   fi
  17 
  18   echo "This script has been running $SECONDS $units."
  19   sleep 1
  20 done
  21 
  22 
  23 exit 0

$REPLY

The default value when a variable is not supplied to read. Also applicable to select menus, but only supplies the item number of the variable chosen, not the value of the variable itself.

   1 #!/bin/bash
   2 
   3 
   4 echo
   5 echo -n "What is your favorite vegetable? "
   6 read
   7 
   8 echo "Your favorite vegetable is $REPLY."
   9 # REPLY holds the value of last "read" if and only if no variable supplied.
  10 
  11 
  12 echo
  13 echo -n "What is your favorite fruit? "
  14 read fruit
  15 echo "Your favorite fruit is $fruit."
  16 echo "but..."
  17 echo "Value of \$REPLY is still $REPLY."
  18 # $REPLY is still set to its previous value because
  19 # the variable $fruit absorbed the new "read" value.
  20 
  21 echo
  22 
  23 exit 0

$SHELLOPTS

the list of enabled shell options, a readonly variable

$BASH

the path to the bash binary itself, usually /bin/bash

$BASH_ENV

an environmental variable pointing to a bash startup file to be read when a script is invoked

$BASH_VERSION

the version of Bash installed on the system

 bash$ echo $BASH_VERSION
 2.04.12(1)-release
 	      

$0, $1, $2, etc.

positional parameters, passed from command line to script, passed to a function, or set to a variable (see Example 3-20 and Example 3-40)

$#

number of command line arguments [1] or positional parameters (see Example 2-4)

$$

process id of script, often used in scripts to construct temp file names (see Example A-5 and Example 3-101)

$?

exit status of command, function, or the script itself (see Example 3-1 and Example 3-56)

$*

All of the positional parameters, seen as a single word

$@

Same as $*, but each parameter is a quoted string, that is, the parameters are passed on intact, without interpretation or expansion. This means, among other things, that each parameter in the argument list is seen as a separate word.


Example 3-17. arglist: Listing arguments with $* and $@

   1 #!/bin/bash
   2 # Invoke this script with several arguments, such as "one two three".
   3 
   4 if [ ! -n "$1" ]
   5 then
   6   echo "Usage: `basename $0` argument1 argument2 etc."
   7   exit 1
   8 fi  
   9 
  10 
  11 echo
  12 
  13 index=1
  14 
  15 echo "Listing args with \"\$*\":"
  16 for arg in "$*"  # Doesn't work properly if "$*" isn't quoted.
  17 do
  18   echo "Arg #$index = $arg"
  19   let "index+=1"
  20 done   # $* sees all arguments as single word. 
  21 echo "Entire arg list seen as single word."
  22 
  23 echo
  24 
  25 index=1
  26 
  27 echo "Listing args with \"\$@\":"
  28 for arg in "$@"
  29 do
  30   echo "Arg #$index = $arg"
  31   let "index+=1"
  32 done   # $@ sees arguments as separate words. 
  33 echo "Arg list seen as separate words."
  34 
  35 echo
  36 
  37 exit 0

The $@ intrinsic variable finds use as a "general input filter" tool in shell scripts. The cat "$@" construction accepts input to a script either from stdin or from files given as parameters to the script. See Example 3-59.

$-

Flags passed to script

Caution

This was originally a ksh construct adopted into Bash, and unfortunately it does not seem to work reliably in Bash scripts. One possible use for it is to have a script self-test whether it is interactive (see Section 3.29).

$!

PID (process id) of last job run in background

variable assignment

Initializing or changing the value of a variable

=

the assignment operator (no space before & after)

Do not confuse this with = and -eq, which test, rather than assign!

Caution

= can be either an assignment or a test operator, depending on context.


Example 3-18. Variable Assignment

   1 #!/bin/bash
   2 
   3 echo
   4 
   5 # When is a variable "naked", i.e., lacking the '$' in front?
   6 
   7 # Assignment
   8 a=879
   9 echo "The value of \"a\" is $a"
  10 
  11 # Assignment using 'let'
  12 let a=16+5
  13 echo "The value of \"a\" is now $a"
  14 
  15 echo
  16 
  17 # In a 'for' loop (really, a type of disguised assignment)
  18 echo -n "The values of \"a\" in the loop are "
  19 for a in 7 8 9 11
  20 do
  21   echo -n "$a "
  22 done
  23 
  24 echo
  25 echo
  26 
  27 # In a 'read' statement
  28 echo -n "Enter \"a\" "
  29 read a
  30 echo "The value of \"a\" is now $a"
  31 
  32 echo
  33 
  34 exit 0


Example 3-19. Variable Assignment, plain and fancy

   1 #!/bin/bash
   2 
   3 a=23
   4 # Simple case
   5 echo $a
   6 b=$a
   7 echo $b
   8 
   9 # Now, getting a little bit fancier...
  10 
  11 a=`echo Hello!`
  12 # Assigns result of 'echo' command to 'a'
  13 echo $a
  14 
  15 a=`ls -l`
  16 # Assigns result of 'ls -l' command to 'a'
  17 echo $a
  18 
  19 exit 0

Variable assignment using the $() mechanism (a newer method than back quotes)

   1 # From /etc/rc.d/rc.local
   2 R=$(cat /etc/redhat-release)
   3 arch=$(uname -m)

local variables

variables visible only within a code block or function (see Section 3.19)

environmental variables

variables that affect the behavior of the shell and user interface, such as the path and the prompt

If a script sets environmental variables, they need to be "exported", that is, reported to the environment local to the script. This is the function of the export command.

Note

A script can export variables only to child processes, that is, only to commands or processes which that particular script initiates. A script invoked from the command line cannot export variables back to the command line environment. Child processes cannot export variables back to the parent processes that spawned them.

---

positional parameters

arguments passed to the script from the command line - $0, $1, $2, $3... ($0 is the name of the script itself, $1 is the first argument, etc.)


Example 3-20. Positional Parameters

   1 #!/bin/bash
   2 
   3 echo
   4 
   5 echo The name of this script is $0
   6 # Adds ./ for current directory
   7 echo The name of this script is `basename $0`
   8 # Strip out path name info (see 'basename')
   9 
  10 echo
  11 
  12 if [ $1 ]
  13 then
  14  echo "Parameter #1 is $1"
  15  # Need quotes to escape #
  16 fi 
  17 
  18 if [ $2 ]
  19 then
  20  echo "Parameter #2 is $2"
  21 fi 
  22 
  23 if [ $3 ]
  24 then
  25  echo "Parameter #3 is $3"
  26 fi 
  27 
  28 echo
  29 
  30 exit 0

Some scripts can perform different operations, depending on which name they are invoked with. For this to work, the script needs to check $0, the name it was invoked by. There also have to be symbolic links present to all the alternate names of the same script.

Note

If a script expects a command line parameter but is invoked without one, this may cause a null variable assignment, certainly an undesirable result. One way to prevent this is to append an extra character to both sides of the assignment statement using the expected positional parameter.

   1 variable1x=$1x
   2 # This will prevent an error, even if positional parameter is absent.
   3 
   4 # The extra character can be stripped off later, if desired, like so.
   5 variable1=${variable1x/x/}
   6 # This uses one of the parameter substitution templates previously discussed.
   7 # Leaving out the replacement pattern results in a deletion.

---


Example 3-21. wh, whois domain name lookup

   1 #!/bin/bash
   2 
   3 # Does a 'whois domain-name' lookup
   4 # on any of 3 alternate servers:
   5 # ripe.net, cw.net, radb.net
   6 
   7 # Place this script, named 'wh' in /usr/local/bin
   8 
   9 # Requires symbolic links:
  10 # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
  11 # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
  12 # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
  13 
  14 
  15 if [ -z $1 ]
  16 then
  17   echo "Usage: `basename $0` [domain-name]"
  18   exit 1
  19 fi
  20 
  21 case `basename $0` in
  22 # Checks script name and calls proper server
  23     "wh"     ) whois $1@whois.ripe.net;;
  24     "wh-ripe") whois $1@whois.ripe.net;;
  25     "wh-radb") whois $1@whois.radb.net;;
  26     "wh-cw"  ) whois $1@whois.cw.net;;
  27     *        ) echo "Usage: `basename $0` [domain-name]";;
  28 esac    
  29 
  30 exit 0

---

The shift command reassigns the positional parameters, in effect shifting them to the left one notch.

$1 <--- $2, $2 <--- $3, $3 <--- $4, etc.

The old $1 disappears, but $0 does not change. If you use a large number of positional parameters to a script, shift lets you access those past 10.


Example 3-22. Using shift

   1 #!/bin/bash
   2 
   3 # Name this script something like shift000,
   4 # and invoke it with some parameters, for example
   5 # ./shift000 a b c def 23 skidoo
   6 
   7 # Demo of using 'shift'
   8 # to step through all the positional parameters.
   9 
  10 until [ -z "$1" ]
  11 do
  12   echo -n "$1 "
  13   shift
  14 done
  15 
  16 echo
  17 # Extra line feed.
  18 
  19 exit 0

3.7.1. Typing variables: declare or typeset

The declare or typeset keywords (they are exact synonyms) permit restricting the properties of variables. This is a very weak form of the typing available in certain programming languages. The declare command is not available in version 1 of bash.

-r readonly

   1 declare -r var1

(declare -r var1 works the same as readonly var1)

This is the rough equivalent of the C const type qualifier. An attempt to change the value of a readonly variable fails with an error message.

-i integer

   1 declare -i var2

The script treats subsequent occurrences of var2 as an integer. Note that certain arithmetic operations are permitted for declared integer variables without the need for expr or let.

-a array

   1 declare -a indices

The variable indices will be treated as an array.

-f functions

   1 declare -f  # (no arguments)

A declare -f line within a script causes a listing of all the functions contained in that script.

-x export

   1 declare -x var3

This declares a variable as available for exporting outside the environment of the script itself.


Example 3-23. Using declare to type variables

   1 #!/bin/bash
   2 
   3 declare -f
   4 # Lists the function below.
   5 
   6 func1 ()
   7 {
   8 echo This is a function.
   9 }
  10 
  11 declare -r var1=13.36
  12 echo "var1 declared as $var1"
  13 # Attempt to change readonly variable.
  14 var1=13.37
  15 # Generates error message.
  16 echo "var1 is still $var1"
  17 
  18 echo
  19 
  20 declare -i var2
  21 var2=2367
  22 echo "var2 declared as $var2"
  23 var2=var2+1
  24 # Integer declaration eliminates the need for 'let'.
  25 echo "var2 incremented by 1 is $var2."
  26 # Attempt to change variable declared as integer
  27 echo "Attempting to change var2 to floating point value, 2367.1."
  28 var2=2367.1
  29 # results in error message, with no change to variable.
  30 echo "var2 is still $var2"
  31 
  32 exit 0

3.7.2. Indirect References to Variables

Assume that the value of a variable is the name of a second variable. Is it somehow possible to retrieve the value of this second variable from the first one? For example, if a=letter_of_alphabet and letter_of_alphabet=z, can a reference to a return z? This can indeed be done, and it is called an indirect reference. It uses the unusual eval var1=\$$var2 notation.


Example 3-24. Indirect References

   1 #!/bin/bash
   2 
   3 # Indirect variable referencing.
   4 
   5 
   6 a=letter_of_alphabet
   7 letter_of_alphabet=z
   8 
   9 # Direct reference.
  10 echo "a = $a"
  11 
  12 # Indirect reference.
  13 eval a=\$$a
  14 echo "Now a = $a"
  15 
  16 echo
  17 
  18 
  19 # Now, let's try changing the second order reference.
  20 
  21 t=table_cell_3
  22 table_cell_3=24
  23 eval t=\$$t
  24 echo "t = $t"
  25 # So far, so good.
  26 
  27 table_cell_3=387
  28 eval t=\$$t
  29 echo "Value of t changed to $t"
  30 # ERROR!
  31 # Cannot indirectly reference changed value of variable this way.
  32 # For this to work, must use ${!t} notation.
  33 
  34 
  35 exit 0

Caution

This method of indirect referencing has a weakness. If the second order variable changes its value, an indirect reference to the first order variable produces an error. Fortunately, this flaw has been fixed in the newer ${!variable} notation introduced with version 2 of Bash (see Example 3-103).

3.7.3. $RANDOM: generate random integer

Note

$RANDOM is an internal Bash function (not a constant) that returns a pseudorandom integer in the range 0 - 32767. $RANDOM should not be used to generate an encryption key.


Example 3-25. Generating random numbers

   1 #!/bin/bash
   2 
   3 # $RANDOM returns a different random integer at each invocation.
   4 # Nominal range: 0 - 32767 (signed integer).
   5 
   6 MAXCOUNT=10
   7 count=1
   8 
   9 echo
  10 echo "$MAXCOUNT random numbers:"
  11 echo "-----------------"
  12 while [ $count -le $MAXCOUNT ]      # Generate 10 ($MAXCOUNT) random integers.
  13 do
  14   number=$RANDOM
  15   echo $number
  16   let "count += 1"  # Increment count.
  17 done
  18 echo "-----------------"
  19 
  20 # If you need a random int within a certain range, then use the 'modulo' operator.
  21 
  22 RANGE=500
  23 
  24 echo
  25 
  26 number=$RANDOM
  27 let "number %= $RANGE"
  28 echo "Random number less than $RANGE  -->  $number"
  29 
  30 echo
  31 
  32 # If you need a random int greater than a lower bound,
  33 # then set up a test to discard all numbers below that.
  34 
  35 FLOOR=200
  36 
  37 number=0   #initialize
  38 while [ $number -le $FLOOR ]
  39 do
  40   number=$RANDOM
  41 done
  42 echo "Random number greater than $FLOOR -->  $number"
  43 echo
  44 
  45 
  46 # May combine above two techniques to retrieve random number between two limits.
  47 number=0   #initialize
  48 while [ $number -le $FLOOR ]
  49 do
  50   number=$RANDOM
  51   let "number %= $RANGE"
  52 done
  53 echo "Random number between $FLOOR and $RANGE -->  $number"
  54 echo
  55 
  56 
  57 # May generate binary choice, that is, "true" or "false" value.
  58 BINARY=2
  59 number=$RANDOM
  60 let "number %= $BINARY"
  61 if [ $number -eq 1 ]
  62 then
  63   echo "TRUE"
  64 else
  65   echo "FALSE"
  66 fi  
  67 
  68 echo
  69 
  70 
  71 # May generate toss of the dice.
  72 SPOTS=7
  73 DICE=2
  74 die1=0
  75 die2=0
  76 
  77 # Tosses each die separately, and so gives correct odds.
  78 
  79   while [ $die1 -eq 0 ]   #Can't have a zero come up.
  80   do
  81     let "die1 = $RANDOM % $SPOTS"
  82   done  
  83 
  84   while [ $die2 -eq 0 ]
  85   do
  86     let "die2 = $RANDOM % $SPOTS"
  87   done  
  88 
  89 let "throw = $die1 + $die2"
  90 echo "Throw of the dice = $throw"
  91 echo
  92 
  93 
  94 exit 0

Note

The variables $USER, $USERNAME, $LOGNAME, $MAIL, and $ENV are not Bash builtins. These are, however, often set as environmental variables in one of the Bash startup files (see Section 3.23). $SHELL is a readonly variable set from /etc/passwd and is likewise not a Bash builtin.

Notes

[1]

The words "argument" and "parameter" are often used interchangeably. In the context of this document, they have the same precise meaning, that of a variable passed to a script or function.