COUNT HOME_DIRECTORY _file1Illegal variable names include:
FILE* The Answer array(i)Some variables are predefined by the shell. They are known as environment variables and special parameters. All other variables are user defined. It is the scripter's responsibility to declare such variables and assign them values.
COUNT=1 HOME_DIRECTORY=/export/home/bugsy _file1=table.datNo white space is permitted between the variable, the equal sign, and the value. If spaces exist in a variable assignment, the shell tries to execute either the variable name of the value as can be seen:
$ COUNT =1 COUNT: not found $ COUNT= 1 1: execute permission denied $ COUNT=1 $To retrieve the value stored in a variable, the variable must be preceded by a dollar sign ($). Getting the value is called dereferencing.
$ COUNT=1 $ echo $COUNT 1Just as the name of a variable cannot contain white space, neither may the value.
$ NAME=John Doe Doe: not foundBut with the help of the shell's quoting mechanisms, it is possible to create strings that do contain white space and then assign them to a variable's value.
$ NAME="John Doe" $ echo $NAME John Doe $ NAME='Jane Doe' $ echo $NAME Jane DoeMoreover, the same quoting techinques can be used to place quotes in a variable's value.
$ ERROR_MSG="You can't do that!" $ echo $ERROR_MSG You can't do that! $ A_QUOTE='"To be, or not to be"' $ echo $A_QUOTE "To be, or not to be"Using quotes, it is even possible to store the output of a command into a variable.
$ XTERMINALS=`ypcat hosts | grep ncd[0-9][0-9] | \ > cut -f2 | cut -f2 -d" " | uniq` $ echo "The NCD xterminals are $XTERMINALS" The NCD xterminals are ncd05 ncd02 ncd03 ncd01 ncd07 ncd04 ncd06Filename expansion can be used to assign values to variables. The usual rules for metacharacters apply.
$ XPROGS=/usr/bin/x* $ echo $XPROGS /usr/bin/xargs /usr/bin/xgettext /usr/bin/xstr $ IPROGS=/usr/bin/i[0-9]* $ echo $IPROGS /usr/bin/i286 /usr/bin/i386 /usr/bin/i486 /usr/bin/i860 /usr/bin/i86pcBraces can be used during variable dereferencing. It is highly recommended that programmers consistently delimit variables with braces. The technique has a few advantages. First, it makes variables standout within a script. Readers can easily see the variable being dereferenced within a block of code. Second, it has the advantage of permitting the generation of expanded strings from variable values. If a script declared a variable named FILE, it could then make a backup copy of the actual value stored in FILE:
$ FILE=oracle.log $ cp ${FILE} ${FILE}.bak $ ls oracle.log oracle.log.bakThis technique would work without the braces, but notice the difference between using the braces and not using them.
$ cp $FILE $FILE.bakIn this case, is it obvious to the reader that the second dereference is the value of FILE with .bak appended to it, or might the reader think that the variable being dereferenced is FILE.bak? By using braces to delimit the variable, the problem is solved. Certainly it is clear that the variable is FILE not FILE.bak. Moreover, there are times when appending text to the variable name may produce unexpected results.
$ cp $FILE $FILE2 cp: Insufficient arguments (1) Usage: cp [-i] [-p] f1 f2 cp [-i] [-p] f1 ... fn d1 cp [-i] [-p] [-r] d1 d2Here, the user attempts to create a copy of oracle.log into oracle.log2, but since digits can be part of a variable name, the shell tries to dereference the undefined variable FILE2. Third, braces allow parameter substitution.
Parameter substitution is a
method of providing a default value for a variable in the event that it
is currently null. The construct uses a combination of braces delimiting
a variable and its default. The variable and default value are separated
by a keyword. The keyword serves as a condition for when to assign the
value to the variable. The list of keywords is shown in the following table.
Construct | Meaning |
---|---|
|
Substitue the value of parameter. |
|
Substitue the value of parameter if it is not null; otherwise, use value. |
|
Substitue the value of parameter if it is not null; otherwise, use value and also assign value to parameter. |
|
Substitue the value of parameter if it is not null; otherwise, write value to standard error and exit. If value is omitted, then write "parameter: parameter null or not set" instead. |
|
Substitue value if parameter is not null; otherwise, substitue nothing. |
$ echo $NULL $ echo "Is it null? : ${NULL} :" Is it null? : : $ echo "Is it null? : ${NULL:-Nope} :" Is it null? : Nope : $ echo "Actually it still is null : ${NULL:?} :" NULL: parameter null or not set $ echo "We'll take care of that : ${NULL:=Nope} :" We'll take care of that : Nope : $ echo "Is it null? : ${NULL} :" Is it null? : Nope :
Variables are read-write by default. Their values may be dereferenced and reset at will. If the need arises, a variable can be declared to be read-only by using the readonly command. The command takes a previously declared variable and applies write protection to it so that the value may not be modified.
$ NOWRITE="Try to change me" $ readonly NOWRITE $ echo ${NOWRITE} Try to change me $ NOWRITE="Ugh, you changed me" NOWRITE: is read onlyIt is important to remember that once a variable is declared read-only, from that point on, it is immutable. Therefore, it is equally important to set the variable's value before applying write protection.
The scope of variables within a script is important to consider as well. Variables defined by a shell script are not necessarily visible to subshells the program spawns. Given a variable x defined in a shell session and given a script local executed during the same session, the value of x is undefined for script local:
$ x=World $ echo ${x} World $ cat local #!/bin/sh echo "Hello ${x}" $ local HelloOn the other hand, it is possible to instruct a shell session to publish its variables to all of its subshells. Called variable exporting, the technique uses the export command. Export takes a list of variables as its arguments. Each variable listed gains global scope for the current session.
$ cat local2 #!/bin/sh echo "${y} ${z}" $ y=Hello $ z=World $ local2 $ export y z $ local2 Hello WorldIn the example above, variables y and z are invisible to local2 as shown by the first invocation, but after applying export to both variables, local2 gains access to them as if it had declared them itself. Now eventhough a shell can publish its variables to subshells, the converse does not hold. Subshells cannot promote local variables to global scope.
$ cat local3 #!/bin/sh a=foo export foo $ b=bar $ echo "${a} ${b}" bar $ local3 $ echo "${a} ${b}" barAlthough local3 attempts to export a to its parent shell, the active session, a does not gain visibility. Only the subshells of local3 would be able to use a. Furthermore, subshells retain a copy of exported variables. They may reassign the value of a global variable, but the reassignment is not permanent. Its effects last only within the subshell. The global variable regains its original value as soon as control is returned to the parent shell.
$ cat local4 #!/bin/sh echo "var1 = ${var1}" var1=100 echo "var1 = ${var1}" $ var1=50 $ export var1 $ local4 var1 = 50 var1 = 100 $ echo ${var1} 50The listing of local4 shows that it prints var1's value and then assigns a value of 100 to it. Var1 is first set to 50 and then exported by the active shell. Local4 is then executed. The output shows that the variable is correctly exported and has its value changed to 100. At this point, the script terminates, and control returns to the interactive session. A quick echo of var1 shows that the global instance of the variable has not changed.
Variable | Meaning |
---|---|
|
The absolute path to the user's home directory. |
|
The internal field separator characters. Usually contains space, tab, and newline. |
|
A colon separated list of directories to search through when trying to execute a command. |
|
The strings to use as the primary, PS1 ($), and secondary prompts, PS2 (>). |
|
The current working directory. |
|
The absolute path to the executable that is being run as the current shell session. |
|
The name of the current account. |
Changing an environment variable is as easy as assigning it a new value. As an example, the current directory could be appended to PATH's value. Doing so would allows a user to execute programs in the current directory without specifying a fully qualified path to the executable.
$ echo ${PATH}
/bin:/usr/bin:/usr/ucb/bin
$ PATH=${PATH}:${PWD}
$ echo ${PATH}
/bin:/usr/bin:/usr/ucb/bin:/home/rsayle
$ cat show-pos-params #!/bin/sh echo ${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} $ show-pos-params testing 1 2 3 ... testing 1 2 3 ... $ show-pos-params One, two, buckle your shoe, three four shut the door One, two, buckle your shoe, three four shut theThere are nine positional parameters available, and each is accessible by dereferencing the argument number. The script show-pos-params listed above demonstrates how to use the positional parameters. The example also shows that only nine arguments can be handled by a script, or a function, at any time. In the second invocation of show-pos-params, the tenth argument, door, does not get printed. It has not vanished and is still available to the script; it is just not readily available via the positional parameters. This could be remedied by shifting the positional parameters so that door moved from the tenth to the ninth position. Shifting is discussed in Section 6, Looping.
An astute reader might now ask about ${0}. Zero is a special positional parameter. It always holds the name of the executing program. Usually, it also includes a path to the program.
$ cat show-pos-param-0 #!/bin/sh echo "This script's name is ${0}" $ show-pos-param-0 This script's name is ./show-pos-param-0The second set of special parameters look a little strange. They seem more like punctuation than variables because each special parameter is a symbol. They are, however, extremely useful for manipulating arguments, processes, and even the currently executing shell.
Parameter | Usage |
---|---|
|
The number of arguments passed to the shell or the number of parameters set by executing the set command. |
|
Returns the values of all the positional parameters as a single value. |
|
Same as $* except when double qouted it has the effect of double quoting each parameter. |
|
Returns the process id of the current shell session or executing script. |
|
Returns the process id of the last program sent to the background. |
|
Returns the exit status of the last command not executed in the background. |
|
Lists the current options in effect (same as executing set with no arguments). |
$ cat show-special-params #!/bin/sh echo "There are $# arguments: $*" echo Or if we needed them quoted, they would be: "$@" echo "If I wanted to backup this file, I might use ${0}.$$" echo "The last echo had exit status = $?" $ show-special-params with some arguments There are 3 arguments: with some arguments Or if we needed them quoted, they would be: with some arguments If I wanted to backup this file, I might use ./show-special-params.2163 The last echo had exit status = 0Notice that in the second echo command, with some arguments was not printed as "with" "some" "arguments". This is a result of how echo treats quoted values; however, with some arguments is actually passed to echo as separately quoted values. This has the advantage of protecting the original parameter values. Without double quotes, the shell could interpret what was meant to be distinct values as one value.
$ cat readit #!/bin/sh TIC_TAC="tic tac" echo "${TIC_TAC} ?" read ANSWER echo "${TIC_TAC} ${ANSWER}" $ readit tic tac ? toe tic tac toeAbove, readit pauses after printing tic tac ? On the next line, the user enters toe. Read receives the value from standard input and stores it in the new variable ANSWER which becomes immediately usable by the program. For read, words are delimited according to the characters defined by the internal field separator variable, IFS. It is usually set to be any white space. The example shows an interactive application; read may also be used to extract values from a file. To do this, however, requires advanced knowledge of how to combine looping constructs with I/O. This is explained later in Section 8.1, More I/O.
Continuing with read's behavior when used interactively, if there are not enough words listed in the line of input, the remaining variables are simply left null.
$ cat not-enough-args #!/bin/sh read a b c d e echo "e = ${e}" $ not-enough-args There aren't enough variables e =On the other hand, the last variable gets all the extra words if the list of variables is shorter than the available words.
$ cat more-than-enough-args #!/bin/sh read a b c echo "c = ${c}" $ more-than-enough-args There are more than enough variables c = more than enough variablesThe point here is that programmers should be aware that unexpected results can occur quite easily when using the read command. It is therefore important to provide rudimentary checks on a variable after reading its value. Section 5, Branching presents the tools to perform such checks.