3.7. Variables Revisited

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

Internal (builtin) variables

variables affecting bash script behavior

$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
 	      

 tcsh% echo $BASH_VERSION
 BASH_VERSION: Undefined variable.
 	      

Checking $BASH_VERSION is a good method of determining which shell is running. $SHELL does not necessarily give the correct answer.

$DIRSTACK

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

This builtin variable is the counterpart to the dirs command.

$EDITOR

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

$EUID

"effective" user id number

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

Caution

The $EUID is not necessarily the same as the $UID.

$GROUPS

groups current user belongs to

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

$HOME

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

$HOSTNAME

The hostname command assigns the system name at bootup in an init script. However, the gethostname() function sets the Bash internal variable $HOSTNAME. See also Example 3-7.

$HOSTTYPE

host type

Like $MACHTYPE, identifies the system hardware.

 bash$ echo $HOSTTYPE
 i686
$IFS

input field separator

This defaults to whitespace (space, tab, and newline), but may be changed, for example, to parse a comma-separated data file. Note that $* uses the first character held in $IFS. See Example 3-12.

 bash$ echo $IFS | cat -vte
 $
 
 
 bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
 w:x:y:z
 	      

Caution

$IFS does not handle whitespace the same as it does other characters.


Example 3-23. $IFS and whitespace

   1 #!/bin/bash
   2 # $IFS treats whitespace differently than other characters.
   3 
   4 output_args_one_per_line()
   5 {
   6   for arg
   7   do echo "[$arg]"
   8   done
   9 }
  10 
  11 echo; echo "IFS=\" \""
  12 echo "-------"
  13 
  14 IFS=" "
  15 var=" a  b c   "
  16 output_args_one_per_line $var  # output_args_one_per_line `echo " a  b c   "`
  17 #
  18 # [a]
  19 # [b]
  20 # [c]
  21 
  22 
  23 echo; echo "IFS=:"
  24 echo "-----"
  25 
  26 IFS=:
  27 var=":a::b:c:::"  # Same as above, but substitute ":" for " ".
  28 output_args_one_per_line $var
  29 #
  30 # []
  31 # [a]
  32 # []
  33 # [b]
  34 # [c]
  35 # []
  36 # []
  37 # []
  38 
  39 # The same thing happens with the "FS" field separator in awk.
  40 
  41 # Thank you, Stephane Chazelas.
  42 
  43 echo
  44 
  45 exit 0

(Thanks, S. C., for clarification and examples.)

$IGNOREEOF

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

$LINENO

This variable is the line number of the shell script in which this variable appears. It has significance only within the script in which it appears, and is chiefly useful for debugging purposes.

   1 last_cmd_arg=$_  # Save it.
   2 
   3 echo "At line number $LINENO, variable \"v1\" = $v1"
   4 echo "Last command argument processed = $last_cmd_arg"

$MACHTYPE

machine type

Identifies the system hardware.

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

old working directory ("OLD-print-working-directory", previous directory you were in)

$OSTYPE

operating system type

 bash$ echo $OSTYPE
 linux-gnu
$PATH

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

When given a command, the shell automatically does a hash table search on 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.25).

 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.

$PPID

The $PPID of a process is the process id (pid) of its parent process. [1]

Compare this with the pidof command.

$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-67).

$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)

This is the analog to the pwd builtin command.

   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 # rm -f .[^.]* ..?*   to remove filenames beginning with multiple dots.
  23 # (shopt -s dotglob; rm -f *)   will also work.
  24 # Thanks, S.C. for pointing this out.
  25 
  26 # Filenames may contain all characters in the 0 - 255 range, except "/".
  27 # Deleting files beginning with weird characters is left as an exercise.
  28 
  29 
  30 # Various other operations here, as necessary.
  31 
  32 echo
  33 echo "Done."
  34 echo "Old files deleted in $TargetDirectory."
  35 echo
  36 
  37 
  38 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

$SECONDS

The number of seconds the script has been running.

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

$SHELLOPTS

the list of enabled shell options, a readonly variable

$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. One method is to set up a timing loop to signal the script when it times out. This also requires a signal handling routine to trap (see Example 3-162) the interrupt generated by the timing loop (whew!).


Example 3-24. Timed Input

   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

An alternative is using stty.


Example 3-25. Once more, timed input

   1 #!/bin/bash
   2 # timeout.sh
   3 
   4 # Written by Stephane Chazelas,
   5 # and modified by the document author.
   6 
   7 INTERVAL=5  # timeout interval
   8 
   9 timedout_read() {
  10   timeout=$1
  11   varname=$2
  12   old_tty_settings=`stty -g`
  13   stty -icanon min 0 time ${timeout}0
  14   eval read $varname     # or just    read $varname
  15   stty "$old_tty_settings"
  16   # See man page for "stty".
  17 }
  18 
  19 echo; echo -n "What's your name? Quick! "
  20 timedout_read $INTERVAL your_name
  21 
  22 # This may not work on every terminal type.
  23 # The maximum timeout depends on the terminal.
  24 # (it is often 25.5 seconds).
  25 
  26 echo
  27 
  28 if [ ! -z "$your_name" ]  # If name input before timeout...
  29 then
  30   echo "Your name is $your_name."
  31 else
  32   echo "Timed out."
  33 fi
  34 
  35 echo
  36 
  37 # This script behaves slightly different than the previous "timed input" example.
  38 # At each keystroke, the counter resets.
  39 
  40 exit 0

$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. $UID is a readonly variable, not subject to change from the command line or within a script, and is the counterpart to the id builtin.


Example 3-26. Am I root?

   1 #!/bin/bash
   2 # am-i-root.sh:   Am I root or not?
   3 
   4 ROOT_UID=0   # Root has $UID 0.
   5 
   6 if [ "$UID" -eq "$ROOT_UID" ]
   7 then
   8   echo "You are root."
   9 else
  10   echo "You are just an ordinary user (but mom loves you just the same)."
  11 fi
  12 
  13 exit 0

See also Example 2-2.

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. $SHELL, the name of the user's login shell, may be set from /etc/passwd or in an "init" script, and it is likewise not a Bash builtin.

$0, $1, $2, etc.

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

$#

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

$$

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

$?

exit status of a command, function, or the script itself (see Example 3-139)

$*

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-27. 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 65
   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 $@ special parameter finds use as a tool for filtering input into 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-98.

Caution

The $* and $@ parameters sometimes display inconsistent and puzzling behavior, depending on the setting of $IFS.


Example 3-28. Inconsistent $* and $@ behavior

   1 #!/bin/bash
   2 
   3 # Erratic behavior of the "$*" and "$@" internal Bash variables,
   4 # depending on whether these are quoted or not.
   5 # Word splitting and linefeeds handled inconsistently.
   6 
   7 # This example script by Stephane Chazelas,
   8 # and slightly modified by the document author.
   9 
  10 
  11 set -- "First one" "second" "third:one" "" "Fifth: :one"
  12 # Setting the script arguments, $1, $2, etc.
  13 
  14 echo
  15 
  16 echo 'IFS unchanged, using "$*"'
  17 c=0
  18 for i in "$*"               # quoted
  19 do echo "$((c+=1)): [$i]"   # This line remains the same in every instance.
  20 # Echo args.
  21 done
  22 echo ---
  23 
  24 echo 'IFS unchanged, using $*'
  25 c=0
  26 for i in $*                 # unquoted
  27 do echo "$((c+=1)): [$i]"
  28 done
  29 echo ---
  30 
  31 echo 'IFS unchanged, using "$@"'
  32 c=0
  33 for i in "$@"
  34 do echo "$((c+=1)): [$i]"
  35 done
  36 echo ---
  37 
  38 echo 'IFS unchanged, using $@'
  39 c=0
  40 for i in $@
  41 do echo "$((c+=1)): [$i]"
  42 done
  43 echo ---
  44 
  45 IFS=:
  46 echo 'IFS=":", using "$*"'
  47 c=0
  48 for i in "$*"
  49 do echo "$((c+=1)): [$i]"
  50 done
  51 echo ---
  52 
  53 echo 'IFS=":", using $*'
  54 c=0
  55 for i in $*
  56 do echo "$((c+=1)): [$i]"
  57 done
  58 echo ---
  59 
  60 var=$*
  61 echo 'IFS=":", using "$var" (var=$*)'
  62 c=0
  63 for i in "$var"
  64 do echo "$((c+=1)): [$i]"
  65 done
  66 echo ---
  67 
  68 echo 'IFS=":", using $var (var=$*)'
  69 c=0
  70 for i in $var
  71 do echo "$((c+=1)): [$i]"
  72 done
  73 echo ---
  74 
  75 var="$*"
  76 echo 'IFS=":", using $var (var="$*")'
  77 c=0
  78 for i in $var
  79 do echo "$((c+=1)): [$i]"
  80 done
  81 echo ---
  82 
  83 echo 'IFS=":", using "$var" (var="$*")'
  84 c=0
  85 for i in "$var"
  86 do echo "$((c+=1)): [$i]"
  87 done
  88 echo ---
  89 
  90 echo 'IFS=":", using "$@"'
  91 c=0
  92 for i in "$@"
  93 do echo "$((c+=1)): [$i]"
  94 done
  95 echo ---
  96 
  97 echo 'IFS=":", using $@'
  98 c=0
  99 for i in $@
 100 do echo "$((c+=1)): [$i]"
 101 done
 102 echo ---
 103 
 104 var=$@
 105 echo 'IFS=":", using $var (var=$@)'
 106 c=0
 107 for i in $var
 108 do echo "$((c+=1)): [$i]"
 109 done
 110 echo ---
 111 
 112 echo 'IFS=":", using "$var" (var=$@)'
 113 c=0
 114 for i in "$var"
 115 do echo "$((c+=1)): [$i]"
 116 done
 117 echo ---
 118 
 119 var="$@"
 120 echo 'IFS=":", using "$var" (var="$@")'
 121 c=0
 122 for i in "$var"
 123 do echo "$((c+=1)): [$i]"
 124 done
 125 echo ---
 126 
 127 echo 'IFS=":", using $var (var="$@")'
 128 c=0
 129 for i in $var
 130 do echo "$((c+=1)): [$i]"
 131 done
 132 
 133 echo
 134 
 135 # Try this script with ksh or zsh -y.
 136 
 137 exit 0

Note

The $@ and $* parameters differ only when between double quotes.


Example 3-29. $* and $@ when $IFS is empty

   1 #!/bin/bash
   2 
   3 # If $IFS set, but empty,
   4 # then "$*" and "$@" do not echo positional params as expected.
   5 
   6 mecho ()  # Echo pos params.
   7 {
   8 echo "$1,$2,$3";
   9 }
  10 
  11 
  12 IFS=""         # Set, but empty.
  13 set a b c      # Positional parameters.
  14 
  15 mecho "$*"     # abc,,
  16 mecho $*       # a,b,c
  17 
  18 mecho $@       # a,b,c
  19 mecho "$@"     # a,b,c
  20 
  21 # The behavior of $* and $@ when $IFS is empty depends
  22 # on whatever Bash or sh version being run.
  23 # It is therefore inadvisable to depend on this "feature" in a script.
  24 
  25 
  26 # Thanks, S.C.
  27 
  28 exit 0

$-

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.

$!

PID (process id) of last job run in background

$_

Special variable set to last argument of previous command executed.


Example 3-30. underscore variable

   1 #!/bin/bash
   2 
   3 echo $_  # bash
   4 # Just executed "bash" to run script.
   5 
   6 du
   7 echo $_  # du
   8 
   9 ls -al
  10 echo $_  # -al
  11 # (last argument)
  12 
  13 :
  14 echo $_  # :

variable assignment

Initializing or changing the value of a variable

=

the assignment operator (no space before & after)

Caution

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

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


Example 3-31. 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-32. 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 backquotes)

   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 also local variables in functions)

environmental variables

variables that affect the behavior of the shell and user interface

Note

In a more general context, each process has an "environment", that is, a group of variables that hold information that the process may reference. In this sense, the shell behaves like any other process.

Every time a shell starts, it creates shell variables that correspond to its own environmental variables. Updating or adding new shell variables causes the shell to update its environment, and all the shell's child processes (the commands it executes) inherit this environment.

Caution

The space allotted to the environment is limited. Creating too many environmental variables or ones that use up excessive space may cause problems.

 bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
 
 bash$ du
 bash: /usr/bin/du: Argument list too long
 	          

(Thank you, S. C. for the clarification, and for providing the above example.)

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, $2 the second, $3 the third, and so forth. [3] After $9, the arguments must be enclosed in brackets, for example, ${10}, ${11}, ${12}.


Example 3-33. Positional Parameters

   1 #!/bin/bash
   2 
   3 # Call this script with at least 10 parameters, for example
   4 # ./scriptname 1 2 3 4 5 6 7 8 9 10
   5 
   6 echo
   7 
   8 echo "The name of this script is \"$0\"."
   9 # Adds ./ for current directory
  10 echo "The name of this script is \"`basename $0`\"."
  11 # Strip out path name info (see 'basename')
  12 
  13 echo
  14 
  15 if [ -n "$1" ]  # Tested variable is quoted.
  16 then
  17  echo "Parameter #1 is $1"
  18  # Need quotes to escape #
  19 fi 
  20 
  21 if [ -n "$2" ]
  22 then
  23  echo "Parameter #2 is $2"
  24 fi 
  25 
  26 if [ -n "$3" ]
  27 then
  28  echo "Parameter #3 is $3"
  29 fi 
  30 
  31 # ...
  32 
  33 if [ -n "${10}" ]  # Parameters > $9 must be enclosed in {brackets}.
  34 then
  35  echo "Parameter #10 is ${10}"
  36 fi 
  37 
  38 echo
  39 
  40 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 must also exist symbolic links to all the alternate names of the script.

Tip

If a script expects a command line parameter but is invoked without one, this may cause a null variable assignment, generally 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 critical_argument01=$variable1x
   5 
   6 # The extra character can be stripped off later, if desired, like so.
   7 variable1=${variable1x/x/}    # It works even if variable1x contains an 'x'.
   8 # This uses one of the parameter substitution templates previously discussed.
   9 # Leaving out the replacement pattern results in a deletion.
  10 
  11 # A more straightforward way of dealing with this is to simply test
  12 # to simply test whether expected positional parameters have been passed.
  13 if [ -z $1 ]
  14 then
  15   exit $POS_PARAMS_MISSING
  16 fi  

---


Example 3-34. 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 65
  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, although {bracket} notation also permits this (see Example 3-33).


Example 3-35. 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 specific to version 2 or later of Bash. The typeset command also works in ksh scripts.

-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

A declare -f line with no arguments in a script causes a listing of all the functions previously defined in that script.

   1 declare -f function_name

A declare -f function_name in a script lists just the function named.

-x export

   1 declare -x var3

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


Example 3-36. Using declare to type variables

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

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-37. Indirect References

   1 #!/bin/bash
   2 
   3 # Indirect variable referencing.
   4 
   5 
   6 a=letter_of_alphabet
   7 letter_of_alphabet=z
   8 
   9 echo
  10 
  11 # Direct reference.
  12 echo "a = $a"
  13 
  14 # Indirect reference.
  15 eval a=\$$a
  16 echo "Now a = $a"
  17 
  18 echo
  19 
  20 
  21 # Now, let's try changing the second order reference.
  22 
  23 t=table_cell_3
  24 table_cell_3=24
  25 echo "\"table_cell_3\" = $table_cell_3"
  26 echo -n "dereferenced \"t\" = "; eval echo \$$t
  27 # In this simple case,
  28 # eval t=\$$t; echo "\"t\" = $t"
  29 # also works (why?).
  30 
  31 echo
  32 
  33 t=table_cell_3
  34 NEW_VAL=387
  35 table_cell_3=$NEW_VAL
  36 echo "Changing value of \"table_cell_3\" to $NEW_VAL."
  37 echo "\"table_cell_3\" now $table_cell_3"
  38 echo -n "dereferenced \"t\" now "; eval echo \$$t
  39 # "eval" takes the two arguments "echo" and "\$$t" (set equal to $table_cell_3)
  40 echo
  41 
  42 # (Thanks, S.C., for clearing up the above behavior.)
  43 
  44 
  45 # Another method is the ${!t} notation, discussed in "Bash, version 2" section.
  46 # See also example "ex78.sh".
  47 
  48 exit 0


Example 3-38. Passing an indirect reference to awk

   1 #!/bin/bash
   2 
   3 # Another version of the "column totaler" script
   4 # that adds up a specified column (of numbers) in the target file.
   5 # This uses indirect references.
   6 
   7 ARGS=2
   8 WRONGARGS=65
   9 
  10 if [ $# -ne "$ARGS" ]
  11 # Check for proper no. of command line args.
  12 then
  13    echo "Usage: `basename $0` filename column-number"
  14    exit $WRONGARGS
  15 fi
  16 
  17 filename=$1
  18 column_number=$2
  19 
  20 #===== Same as original script, up to this point =====#
  21 
  22 
  23 # A multi-line awk script is invoked by   awk ' ..... '
  24 
  25 
  26 # Begin awk script.
  27 # ------------------------------------------------
  28 awk "
  29 
  30 { total += \$${column_number} # indirect reference
  31 }
  32 END {
  33      print total
  34      }
  35 
  36      " "$filename"
  37 # ------------------------------------------------
  38 # End awk script.
  39 
  40 # Indirect variable reference avoids the hassles
  41 # of referencing a shell variable within the embedded awk script.
  42 # Thanks, Stephane Chazelas.
  43 
  44 
  45 exit 0

Caution

This method of indirect referencing is a bit tricky. If the second order variable changes its value, then the the first order variable must be properly dereferenced (as in the above example). Fortunately, the ${!variable} notation introduced with version 2 of Bash (see Example 3-167) makes indirect referencing more intuitive.

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-39. 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 # This returns the remainder of a division operation.
  22 
  23 RANGE=500
  24 
  25 echo
  26 
  27 number=$RANDOM
  28 let "number %= $RANGE"
  29 echo "Random number less than $RANGE  -->  $number"
  30 
  31 echo
  32 
  33 # If you need a random int greater than a lower bound,
  34 # then set up a test to discard all numbers below that.
  35 
  36 FLOOR=200
  37 
  38 number=0   #initialize
  39 while [ "$number" -le $FLOOR ]
  40 do
  41   number=$RANDOM
  42 done
  43 echo "Random number greater than $FLOOR -->  $number"
  44 echo
  45 
  46 
  47 # May combine above two techniques to retrieve random number between two limits.
  48 number=0   #initialize
  49 while [ "$number" -le $FLOOR ]
  50 do
  51   number=$RANDOM
  52   let "number %= $RANGE"  # Scales $number down within $RANGE.
  53 done
  54 echo "Random number between $FLOOR and $RANGE -->  $number"
  55 echo
  56 
  57 
  58 # May generate binary choice, that is, "true" or "false" value.
  59 BINARY=2
  60 number=$RANDOM
  61 T=1
  62 
  63 let "number %= $BINARY"
  64 # let "number >>= 14"    gives a better random distribution
  65 # (right shifts out everything except last binary digit).
  66 if [ "$number" -eq $T ]
  67 then
  68   echo "TRUE"
  69 else
  70   echo "FALSE"
  71 fi  
  72 
  73 echo
  74 
  75 
  76 # May generate toss of the dice.
  77 SPOTS=7
  78 DICE=2
  79 ZERO=0
  80 die1=0
  81 die2=0
  82 
  83 # Tosses each die separately, and so gives correct odds.
  84 
  85   while [ "$die1" -eq $ZERO ]   #Can't have a zero come up.
  86   do
  87     let "die1 = $RANDOM % $SPOTS"
  88   done  
  89 
  90   while [ "$die2" -eq $ZERO ]
  91   do
  92     let "die2 = $RANDOM % $SPOTS"
  93   done  
  94 
  95 let "throw = $die1 + $die2"
  96 echo "Throw of the dice = $throw"
  97 echo
  98 
  99 
 100 exit 0

Just how random is RANDOM? The best way to test this is to write a script that tracks the distribution of "random" numbers generated by RANDOM. Let's roll a RANDOM die a few times...


Example 3-40. Rolling the die with RANDOM

   1 #!/bin/bash
   2 # Test RANDOM for randomness.
   3 
   4 RANDOM=$$       # Reseed the random number generator using process ID.
   5 
   6 PIPS=6          # A die has 6 pips.
   7 MAXTHROWS=600   # Increase this, if you have nothing better to do with your time.
   8 throw=0         # Throw count.
   9 
  10 zeroes=0        # Must initialize counts to zero.
  11 ones=0          # since an uninitialized variable is null, not zero.
  12 twos=0
  13 threes=0
  14 fours=0
  15 fives=0
  16 sixes=0
  17 
  18 print_result ()
  19 {
  20 echo
  21 echo "ones =   $ones"
  22 echo "twos =   $twos"
  23 echo "threes = $threes"
  24 echo "fours =  $fours"
  25 echo "fives =  $fives"
  26 echo "sixes =  $sixes"
  27 echo
  28 }
  29 
  30 update_count()
  31 {
  32 case "$1" in
  33   0) let "ones += 1";;   # Since die has no "zero", this corresponds to 1.
  34   1) let "twos += 1";;   # And this to 2, etc.
  35   2) let "threes += 1";;
  36   3) let "fours += 1";;
  37   4) let "fives += 1";;
  38   5) let "sixes += 1";;
  39 esac
  40 }
  41 
  42 echo
  43 
  44 
  45 while [ "$throw" -lt "$MAXTHROWS" ]
  46 do
  47   let "die1 = RANDOM % $PIPS"
  48   update_count $die1
  49   let "throw += 1"
  50 done  
  51 
  52 print_result
  53 
  54 # The scores should distribute fairly evenly, assuming RANDOM is fairly random.
  55 # With $MAXTHROWS at 600, all should cluster around 100, plus-or-minus 20 or so.
  56 #
  57 # Keep in mind that RANDOM is a pseudorandom generator,
  58 # and not a spectacularly good one at that.
  59 
  60 # Exercise for the reader (easy):
  61 # Rewrite this script to flip a coin 1000 times.
  62 # Choices are "HEADS" or "TAILS".
  63 
  64 exit 0

As we have seen in the last example, it is best to "reseed" the RANDOM generator each time it is invoked. Using the same seed for RANDOM repeats the same series of numbers. (This mirrors the behavior of the random() function in C.)


Example 3-41. Reseeding RANDOM

   1 #!/bin/bash
   2 # Seeding the RANDOM variable.
   3 
   4 MAXCOUNT=25   # How many numbers to generate.
   5 
   6 random_numbers ()
   7 {
   8 count=0
   9 while [ "$count" -lt "$MAXCOUNT" ]
  10 do
  11   number=$RANDOM
  12   echo -n "$number "
  13   let "count += 1"
  14 done  
  15 }
  16 
  17 echo; echo
  18 
  19 RANDOM=1   # Setting RANDOM seeds the random number generator.
  20 random_numbers
  21 
  22 echo; echo
  23 
  24 RANDOM=1          # Same seed for RANDOM...
  25 random_numbers    # ...reproduces the exact same number series.
  26 
  27 echo; echo
  28 
  29 RANDOM=2          # Trying again, but with a different seen...
  30 random_numbers    # gives a different number series.
  31 
  32 echo; echo
  33 
  34 # RANDOM=$$  seeds RANDOM from process id of script.
  35 # It is also possible to seed RANDOM from 'time' or 'date'.
  36 
  37 # Getting fancy...
  38 SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
  39 # Pseudo-random output fetched from /dev/urandom (system pseudo-random "device"),
  40 # then converted to line of printable (octal) numbers by "od",
  41 # finally "awk" retrieves just one number for SEED.
  42 RANDOM=$SEED
  43 random_numbers
  44 
  45 echo; echo
  46 
  47 exit 0

Note

The /dev/urandom device/file provides a means of generating much more "random" pseudorandom numbers than the $RANDOM variable. dd if=/dev/urandom of=targetfile bs=1 count=XX creates a file of well-scattered pseudorandom numbers. However, assigning these numbers to a variable in a script requires a workaround, such as filtering through od (as in above example).

3.7.4. The Double Parentheses Construct

Similar to the let command, the ((...)) construct permits arithmetic expansion and evaluation. In its simplest form, a=$(( 5 + 3 )) would set "a" to "5 + 3", or 8. However, this double parentheses construct is also a mechanism for allowing C-type manipulation of variables in Bash.


Example 3-42. C-type manipulation of variables

   1 #!/bin/bash
   2 # Manipulating a variable, C-style, using the ((...)) construct.
   3 
   4 echo
   5 
   6 (( a = 23 ))  # Setting a value, C-style, with spaces on both sides of the "=".
   7 echo "a (initial value) = $a"
   8 
   9 (( a++ ))  # Post-increment 'a', C-style.
  10 echo "a (after a++) = $a"
  11 
  12 (( a-- ))  # Post-decrement 'a', C-style.
  13 echo "a (after a--) = $a"
  14 
  15 
  16 (( ++a ))  # Pre-increment 'a', C-style.
  17 echo "a (after ++a) = $a"
  18 
  19 (( --a ))  # Pre-decrement 'a', C-style.
  20 echo "a (after --a) = $a"
  21 
  22 echo
  23 
  24 (( t = a<45?7:11 ))   # C-style trinary operator.
  25 echo "If a < 45, then t = 7, else t = 11."
  26 echo "t = $t "  # Yes!
  27 
  28 echo
  29 
  30 
  31 # -----------------
  32 # Easter Egg alert!
  33 # -----------------
  34 # Chet Ramey apparently snuck a bunch of undocumented C-style constructs into Bash.
  35 # In the Bash docs, Ramey calls ((...)) shell arithmetic, but it goes far beyond that.
  36 # Sorry, Chet, the secret is now out.
  37 
  38 # See also "for" and "while" loops using the ((...)) construct.
  39 
  40 # Note: these may not work with early versions of Bash,
  41 #       or on other platforms besides Linux.
  42 
  43 exit 0

See also Example 3-52.

Notes

[1]

The pid of the currently running script is $$, of course.

[2]

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.

[3]

The process calling the script sets the $0 parameter. By convention, this parameter is the name of the script. See the man page for execv.