|
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) #!/bin/bash
WRONG_DIRECTORY=33
clear # Clear screen.
TargetDirectory=/home/bozo/projects/GreatAmericanNovel
cd $TargetDirectory
echo "Deleting stale files in $TargetDirectory."
if [ $PWD != $TargetDirectory ] # Keep from wiping out wrong directory by accident.
then
echo "Wrong directory!"
echo "In $PWD, rather than $TargetDirectory!"
echo "Bailing out!"
exit $WRONG_DIRECTORY
fi
rm -rf *
rm .[A-Za-z0-9]* # Delete dotfiles.
# Various other operations here, as necessary.
echo
echo "Done."
echo "Old files deleted in $TargetDirectory."
echo
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!). #!/bin/bash
# TMOUT=3 useless in a script
TIMELIMIT=3 # Three seconds in this instance, may be set to different value.
PrintAnswer()
{
if [ $answer = TIMEOUT ]
then
echo $answer
else # Don't want to mix up the two instances.
echo "Your favorite veggie is $answer"
kill $! # Kills no longer needed TimerOn function running in background.
# $! is PID of last job running in background.
fi
}
TimerOn()
{
sleep $TIMELIMIT && kill -s 14 $$ &
# Waits 3 seconds, then sends sigalarm to script.
}
Int14Vector()
{
answer="TIMEOUT"
PrintAnswer
exit 14
}
trap Int14Vector 14
# Timer interrupt - 14 - subverted for our purposes.
echo "What is your favorite vegetable "
TimerOn
read answer
PrintAnswer
# Admittedly, this is a kludgy implementation of timed input,
# but pretty much as good as can be done with Bash.
# (Challenge to reader: come up with something better.)
# If you need something a bit more elegant...
# consider writing the application in C or C++,
# using appropriate library functions, such as 'alarm' and 'setitimer'.
exit 0 |
- $SECONDS
The number of seconds the script has been running. #!/bin/bash
ENDLESS_LOOP=1
echo
echo "Hit Control-C to exit this script."
echo
while [ $ENDLESS_LOOP ]
do
if [ $SECONDS -eq 1 ]
then
units=second
else
units=seconds
fi
echo "This script has been running $SECONDS $units."
sleep 1
done
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. #!/bin/bash
echo
echo -n "What is your favorite vegetable? "
read
echo "Your favorite vegetable is $REPLY."
# REPLY holds the value of last "read" if and only if no variable supplied.
echo
echo -n "What is your favorite fruit? "
read fruit
echo "Your favorite fruit is $fruit."
echo "but..."
echo "Value of \$REPLY is still $REPLY."
# $REPLY is still set to its previous value because
# the variable $fruit absorbed the new "read" value.
echo
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 $@ #!/bin/bash
# Invoke this script with several arguments, such as "one two three".
if [ ! -n "$1" ]
then
echo "Usage: `basename $0` argument1 argument2 etc."
exit 1
fi
echo
index=1
echo "Listing args with \"\$*\":"
for arg in "$*" # Doesn't work properly if "$*" isn't quoted.
do
echo "Arg #$index = $arg"
let "index+=1"
done # $* sees all arguments as single word.
echo "Entire arg list seen as single word."
echo
index=1
echo "Listing args with \"\$@\":"
for arg in "$@"
do
echo "Arg #$index = $arg"
let "index+=1"
done # $@ sees arguments as separate words.
echo "Arg list seen as separate words."
echo
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 #!/bin/bash
echo
# When is a variable "naked", i.e., lacking the '$' in front?
# Assignment
a=879
echo "The value of \"a\" is $a"
# Assignment using 'let'
let a=16+5
echo "The value of \"a\" is now $a"
echo
# In a 'for' loop (really, a type of disguised assignment)
echo -n "The values of \"a\" in the loop are "
for a in 7 8 9 11
do
echo -n "$a "
done
echo
echo
# In a 'read' statement
echo -n "Enter \"a\" "
read a
echo "The value of \"a\" is now $a"
echo
exit 0 |
Example 3-19. Variable Assignment, plain and fancy #!/bin/bash
a=23
# Simple case
echo $a
b=$a
echo $b
# Now, getting a little bit fancier...
a=`echo Hello!`
# Assigns result of 'echo' command to 'a'
echo $a
a=`ls -l`
# Assigns result of 'ls -l' command to 'a'
echo $a
exit 0 |
Variable assignment using the $() mechanism
(a newer method than back quotes) # From /etc/rc.d/rc.local
R=$(cat /etc/redhat-release)
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 #!/bin/bash
echo
echo The name of this script is $0
# Adds ./ for current directory
echo The name of this script is `basename $0`
# Strip out path name info (see 'basename')
echo
if [ $1 ]
then
echo "Parameter #1 is $1"
# Need quotes to escape #
fi
if [ $2 ]
then
echo "Parameter #2 is $2"
fi
if [ $3 ]
then
echo "Parameter #3 is $3"
fi
echo
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.
variable1x=$1x
# This will prevent an error, even if positional parameter is absent.
# The extra character can be stripped off later, if desired, like so.
variable1=${variable1x/x/}
# This uses one of the parameter substitution templates previously discussed.
# Leaving out the replacement pattern results in a deletion. |
--- Example 3-21. wh, whois domain name lookup #!/bin/bash
# Does a 'whois domain-name' lookup
# on any of 3 alternate servers:
# ripe.net, cw.net, radb.net
# Place this script, named 'wh' in /usr/local/bin
# Requires symbolic links:
# ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
# ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
# ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
if [ -z $1 ]
then
echo "Usage: `basename $0` [domain-name]"
exit 1
fi
case `basename $0` in
# Checks script name and calls proper server
"wh" ) whois $1@whois.ripe.net;;
"wh-ripe") whois $1@whois.ripe.net;;
"wh-radb") whois $1@whois.radb.net;;
"wh-cw" ) whois $1@whois.cw.net;;
* ) echo "Usage: `basename $0` [domain-name]";;
esac
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 #!/bin/bash
# Name this script something like shift000,
# and invoke it with some parameters, for example
# ./shift000 a b c def 23 skidoo
# Demo of using 'shift'
# to step through all the positional parameters.
until [ -z "$1" ]
do
echo -n "$1 "
shift
done
echo
# Extra line feed.
exit 0 |
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
(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
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
The variable indices will be treated as
an array. - -f functions
declare -f # (no arguments) |
A declare -f line within a script causes
a listing of all the functions contained in that script. - -x export
This declares a variable as available for exporting outside the
environment of the script itself.
Example 3-23. Using declare to type variables #!/bin/bash
declare -f
# Lists the function below.
func1 ()
{
echo This is a function.
}
declare -r var1=13.36
echo "var1 declared as $var1"
# Attempt to change readonly variable.
var1=13.37
# Generates error message.
echo "var1 is still $var1"
echo
declare -i var2
var2=2367
echo "var2 declared as $var2"
var2=var2+1
# Integer declaration eliminates the need for 'let'.
echo "var2 incremented by 1 is $var2."
# Attempt to change variable declared as integer
echo "Attempting to change var2 to floating point value, 2367.1."
var2=2367.1
# results in error message, with no change to variable.
echo "var2 is still $var2"
exit 0 |
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 #!/bin/bash
# Indirect variable referencing.
a=letter_of_alphabet
letter_of_alphabet=z
# Direct reference.
echo "a = $a"
# Indirect reference.
eval a=\$$a
echo "Now a = $a"
echo
# Now, let's try changing the second order reference.
t=table_cell_3
table_cell_3=24
eval t=\$$t
echo "t = $t"
# So far, so good.
table_cell_3=387
eval t=\$$t
echo "Value of t changed to $t"
# ERROR!
# Cannot indirectly reference changed value of variable this way.
# For this to work, must use ${!t} notation.
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). |
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 #!/bin/bash
# $RANDOM returns a different random integer at each invocation.
# Nominal range: 0 - 32767 (signed integer).
MAXCOUNT=10
count=1
echo
echo "$MAXCOUNT random numbers:"
echo "-----------------"
while [ $count -le $MAXCOUNT ] # Generate 10 ($MAXCOUNT) random integers.
do
number=$RANDOM
echo $number
let "count += 1" # Increment count.
done
echo "-----------------"
# If you need a random int within a certain range, then use the 'modulo' operator.
RANGE=500
echo
number=$RANDOM
let "number %= $RANGE"
echo "Random number less than $RANGE --> $number"
echo
# If you need a random int greater than a lower bound,
# then set up a test to discard all numbers below that.
FLOOR=200
number=0 #initialize
while [ $number -le $FLOOR ]
do
number=$RANDOM
done
echo "Random number greater than $FLOOR --> $number"
echo
# May combine above two techniques to retrieve random number between two limits.
number=0 #initialize
while [ $number -le $FLOOR ]
do
number=$RANDOM
let "number %= $RANGE"
done
echo "Random number between $FLOOR and $RANGE --> $number"
echo
# May generate binary choice, that is, "true" or "false" value.
BINARY=2
number=$RANDOM
let "number %= $BINARY"
if [ $number -eq 1 ]
then
echo "TRUE"
else
echo "FALSE"
fi
echo
# May generate toss of the dice.
SPOTS=7
DICE=2
die1=0
die2=0
# Tosses each die separately, and so gives correct odds.
while [ $die1 -eq 0 ] #Can't have a zero come up.
do
let "die1 = $RANDOM % $SPOTS"
done
while [ $die2 -eq 0 ]
do
let "die2 = $RANDOM % $SPOTS"
done
let "throw = $die1 + $die2"
echo "Throw of the dice = $throw"
echo
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.
|