Parameters and Variables


Shell parameters and variables were introduced on page 285. This section adds to the previous coverage with a discussion of array variables, global versus local variables, special and positional parameters, and expanding null and unset variables.

Array Variables

The Bourne Again Shell supports one-dimensional array variables. The subscripts are integers with zero-based indexing (i.e., the first element of the array has the subscript 0). The following format declares and assigns values to an array:



name=(element1 element2 ...)

The following example assigns four values to the array NAMES:

$ NAMES=(max helen sam zach)


You reference a single element of an array as follows:

$ echo ${NAMES[2]} sam


The subscripts [*] and [@] both extract the entire array but work differently when used within double quotation marks. An @ produces an array that is a duplicate of the original array; an * produces a single element of an array (or a plain variable) that holds all the elements of the array separated by the first character in IFS (normally a SPACE). In the following example, the array A is filled with the elements of the NAMES variable using an *, and B is filled using an @. The declare builtin with the a option displays the values of the arrays (and reminds you that bash uses zero-based indexing for arrays):

$ A=("${NAMES[*]}") $ B=("${NAMES[@]}") $ declare -a declare -a A='([0]="max helen sam zach")' declare -a B='([0]="max" [1]="helen" [2]="sam" [3]="zach")' ... declare -a NAMES='([0]="max" [1]="helen" [2]="sam" [3]="zach")'


From the output of declare, you can see that NAMES and B have multiple elements. In contrast, A, which was assigned its value with an * within double quotation marks, has only one element: A has all its elements enclosed between double quotation marks.

In the next example, echo attempts to display element 1 of array A. Nothing is displayed because A has only one element and that element has an index of 0. Element 0 of array A holds all four names. Element 1 of B holds the second item in the array and element 0 holds the first item.

$  echo ${A[1]} $  echo ${A[0]} max helen sam zach $  echo ${B[1]} helen $  echo ${B[0]} max


You can apply the ${#name[*]} operator to array variables, returning the number of elements in the array:

$ echo ${#NAMES[*]} 4


The same operator, when given the index of an element of an array in place of *, returns the length of the element:

$ echo ${#NAMES[1]} 5


You can use subscripts on the left side of an assignment statement to replace selected elements of the array:

$ NAMES[1]=alex $ echo ${NAMES[*]} max alex sam zach


Locality of Variables

By default variables are local to the process in which they are declared. Thus a shell script does not have access to variables declared in your login shell unless you explicitly make the variables available (global). Under bash, export makes a variable available to child processes.

Once you use the export builtin with a variable name as an argument, the shell places the value of the variable in the calling environment of child processes. This call by value gives each child process a copy of the variable for its own use.

The following extest1 shell script assigns a value of american to the variable named cheese and then displays its filename (extest1) and the value of cheese. The extest1 script then calls subtest, which attempts to display the same information. Next subtest declares a cheese variable and displays its value. When subtest finishes, it returns control to the parent process, which is executing extest1. At this point extest1 again displays the value of the original cheese variable.

$  cat extest1 cheese=american echo "extest1 1: $cheese" subtest echo "extest1 2: $cheese" $  cat subtest echo "subtest 1: $cheese" cheese=swiss echo "subtest 2: $cheese" $ extest1 extest1 1: american subtest 1: subtest 2: swiss extest1 2: american


The subtest script never receives the value of cheese from extest1, and extest1 never loses the value. Unlike in the real world, a child can never affect its parent's attributes. When a process attempts to display the value of a variable that has not been declared, as is the case with subtest, the process displays nothing; the value of an undeclared variable is that of a null string.

The following extest2 script is the same as extest1 except that it uses export to make cheese available to the subtest script:

$ cat extest2 export cheese=american echo "extest2 1: $cheese" subtest echo "extest2 2: $cheese" $ extest2 extest2 1: american subtest 1: american subtest 2: swiss extest2 2: american


Here the child process inherits the value of cheese as american and, after displaying this value, changes its copy to swiss. When control is returned to the parent, the parent's copy of cheese retains its original value: american.

An export builtin can optionally include an assignment:

export cheese=american


The preceding statement is equivalent to the following two statements:

cheese=american export cheese


Although it is rarely done, you can export a variable before you assign a value to it. You do not need to export an already-exported variable a second time after you change its value. For example, you do not usually need to export PATH when you assign a value to it in ~/.bash_profile because it is typically exported in the /etc/profile global startup file.

Functions

Because functions run in the same environment as the shell that calls them, variables are implicitly shared by a shell and a function it calls.

$ function nam () { > echo $myname > myname=zach > } $ myname=sam $ nam sam $ echo $myname zach


In the preceding example, the myname variable is set to sam in the interactive shell. Then the nam function is called. It displays the value of myname it has (sam) and sets myname to zach. The final echo shows that, in the interactive shell, the value of myname has been changed to zach.

Function local variables


Local variables are helpful in a function written for general use. Because the function is called by many scripts that may be written by different programmers, you need to make sure that the names of the variables used within the function do not interact with variables of the same name in the programs that call the function. Local variables eliminate this problem. When used within a function, the typeset builtin declares a variable to be local to the function it is defined in.

The next example shows the use of a local variable in a function. It uses two variables named count. The first is declared and assigned a value of 10 in the interactive shell. Its value never changes, as echo verifies after count_down is run. The other count is declared, using typeset, to be local to the function. Its value, which is unknown outside the function, ranges from 4 to 1, as the echo command within the function confirms.

The example shows the function being entered from the keyboard; it is not a shell script. (See the tip "A function is not a shell script" on page 912).

$ function count_down () { > typeset count > count=$1 > while [ $count -gt 0 ] > do > echo "$count..." > ((count=count-1)) > sleep 1 > done > echo "Blast Off." > } $  count=10 $ count_down 4 4... 3... 2... 1... Blast Off\! $ echo $count 10


The ((count=count1)) assignment is enclosed between double parentheses, which cause the shell to perform an arithmetic evaluation (page 940). Within the double parentheses you can reference shell variables without the leading dollar sign ($).

Special Parameters

Special parameters enable you to access useful values pertaining to command line arguments and the execution of shell commands. You reference a shell special parameter by preceding a special character with a dollar sign ($). As with positional parameters, it is not possible to modify the value of a special parameter by assignment.

$$: PID Number

The shell stores in the $$ parameter the PID number of the process that is executing it. In the following interaction, echo displays the value of this variable and the ps utility confirms its value. Both commands show that the shell has a PID number of 5209:

$ echo $$ 5209 $ ps   PID TTY          TIME CMD  5209 pts/1    00:00:00 bash  6015 pts/1    00:00:00 ps


Because echo is built into the shell, the shell does not have to create another process when you give an echo command. However, the results are the same whether echo is a builtin or not, because the shell substitutes the value of $$ before it forks a new process to run a command. Try using the echo utility (/bin/echo), which is run by another process, and see what happens. In the following example, the shell substitutes the value of $$ and passes that value to cp as a prefix for a filename:

$ echo $$ 8232 $ cp memo $$.memo $ ls 8232.memo memo


Incorporating a PID number in a filename is useful for creating unique filenames when the meanings of the names do not matter; it is often used in shell scripts for creating names of temporary files. When two people are running the same shell script, these unique filenames keep them from inadvertently sharing the same temporary file.

The following example demonstrates that the shell creates a new shell process when it runs a shell script. The id2 script displays the PID number of the process running it (not the process that called itthe substitution for $$ is performed by the shell that is forked to run id2):

$  cat id2 echo "$0 PID= $$" $  echo $$ 8232 $ id2 ./id2 PID= 8362 $  echo $$ 8232


The first echo displays the PID number of the interactive shell. Then id2 displays its name ($0) and the PID of the subshell that it is running in. The last echo shows that the PID number of the interactive shell has not changed.

$!


The value of the PID number of the last process that you ran in the background is stored in $!. The following example executes sleep as a background task and uses echo to display the value of $!:

$ sleep 60 & [1] 8376 $ echo $! 8376


$?: Exit Status

When a process stops executing for any reason, it returns an exit status to the parent process. The exit status is also referred to as a condition code or a return code. The $? variable stores the exit status of the last command.

By convention a nonzero exit status represents a false value and means that the command failed. A zero is true and indicates that the command was successful. In the following example, the first ls command succeeds and the second fails:

$ ls es es $ echo $? 0 $ ls xxx ls: xxx: No such file or directory $  echo $? 1


You can specify the exit status that a shell script returns by using the exit builtin, followed by a number, to terminate the script. If you do not use exit with a number to terminate a script, the exit status of the script is that of the last command the script ran.

$ cat es echo This program returns an exit status of 7. exit 7 $ es This program returns an exit status of 7. $  echo $? 7 $  echo $? 0


The es shell script displays a message and terminates execution with an exit command that returns an exit status of 7, the user-defined exit status in this script. The first echo then displays the value of the exit status of es. The second echo displays the value of the exit status of the first echo. The value is 0 because the first echo was successful.

Positional Parameters

The positional parameters comprise the command name and command line arguments. They are called positional because within a shell script, you refer to them by their position on the command line. Only the set builtin (page 924) allows you to change the values of positional parameters with one exception: You cannot change the value of the command name from within a script.

$#: Number of Command Line Arguments

The $# parameter holds the number of arguments on the command line (positional parameters), not counting the command itself:

$ cat num_args echo "This script was called with $# arguments." $ num_args sam max zach This script was called with 3 arguments.


$0: Name of the Calling Program

The shell stores the name of the command you used to call a program in parameter $0. This parameter is numbered zero because it appears before the first argument on the command line:

$ cat abc echo "The command used to run this script is $0" $ abc The command used to run this script is ./abc $ /home/sam/abc The command used to run this script is /home/sam/abc


The preceding shell script uses echo to verify the name of the script you are executing. You can use the basename utility and command substitution to extract and display the simple filename of the command:

$ cat abc2 echo "The command used to run this script is $(basename $0)" $ /home/sam/abc2 The command used to run this script is abc2


$1$n: Command Line Arguments

The first argument on the command line is represented by parameter $1, the second argument by $2, and so on up to $n. For values of n over 9, the number must be enclosed within braces. For example, the twelfth command line argument is represented by ${12}. The following script displays positional parameters that hold command line arguments:

$ cat display_5args echo First 5 arguments are $1 $2 $3 $4 $5 $ display_5args jenny alex helen First 5 arguments are jenny alex helen


The display_5args script displays the first five command line arguments. The shell assigns a null value to each parameter that represents an argument that is not present on the command line. Thus the $4 and $5 variables have null values in this example.

$*


The $ * variable represents all the command line arguments, as the display_all program demonstrates:

$ cat display_all echo All arguments are $* $ display_all a b c d e f g h i j k l m n o p All arguments are a b c d e f g h i j k l m n o p


Enclose references to positional parameters between double quotation marks. The quotation marks are particularly important when you are using positional parameters as arguments to commands. Without double quotation marks, a positional parameter that is not set or that has a null value disappears:

$ cat showargs echo "$0 was called with $# arguments, the first is :$1:." $ showargs a b c ./showargs was called with 3 arguments, the first is :a:. $ echo $xx $ showargs $xx a b c ./showargs was called with 3 arguments, the first is :a:. $ showargs "$xx" a b c ./showargs was called with 4 arguments, the first is ::.


The showargs script displays the number of arguments ($#) followed by the value of the first argument enclosed between colons. The preceding example first calls showargs with three simple arguments. Next the echo command demonstrates that the $xx variable, which is not set, has a null value. In the final two calls to showargs, the first argument is $xx. In the first case the command line becomes showargs a b c; the shell passes showargs three arguments. In the second case the command line becomes showargs " " a b c, which results in calling showargs with four arguments. The difference in the two calls to showargs illustrates a subtle potential problem that you should keep in mind when using positional parameters that may not be set or that may have a null value.

"$*" versus "$@"


The $* and $@ parameters work the same way except when they are enclosed within double quotation marks. Using "$*" yields a single argument (with SPACEs or the value of IFS [page 295] between the positional parameters), whereas "$@" produces a list wherein each positional parameter is a separate argument. This difference typically makes "$@" more useful than "$*" in shell scripts.

The following scripts help to explain the difference between these two special parameters. In the second line of both scripts, the single quotation marks keep the shell from interpreting the enclosed special characters so they can be displayed as themselves. The bb1 script shows that set "$*" assigns multiple arguments to the first command line parameter:

$ cat bb1 set "$*" echo $# parameters with '"$*"' echo 1: $1 echo 2: $2 echo 3: $3 $ bb1 a b c 1 parameters with "$*" 1: a b c 2: 3:


The bb2 script shows that set "$@" assigns each argument to a different command line parameter:

$ cat bb2 set "$@" echo $# parameters with '"$@"' echo 1: $1 echo 2: $2 echo 3: $3 $ bb2 a b c 3 parameters with "$@" 1: a 2: b 3: c


shift: Promotes Command Line Arguments

The shift builtin promotes each command line argument. The first argument (which was $1) is discarded. The second argument (which was $2) becomes the first argument (now $1), the third becomes the second, and so on. Because no "unshift" command exists, you cannot bring back arguments that have been discarded. An optional argument to shift specifies the number of positions to shift (and the number of arguments to discard); the default is 1.

The following demo_shift script is called with three arguments. Double quotation marks around the arguments to echo preserve the spacing of the output. The program displays the arguments and shifts them repeatedly until there are no more arguments left to shift:

$ cat demo_shift echo "arg1= $1    arg2= $2     arg3= $3" shift echo "arg1= $1    arg2= $2     arg3= $3" shift echo "arg1= $1    arg2= $2     arg3= $3" shift echo "arg1= $1    arg2= $2     arg3= $3" shift $ demo_shift alice helen jenny arg1= alice    arg2= helen    arg3= jenny arg1= helen    arg2= jenny    arg3= arg1= jenny    arg2=    arg3= arg1=     arg2=    arg3=


Repeatedly using shift is a convenient way to loop over all the command line arguments in shell scripts that expect an arbitrary number of arguments. See page 883 for a shell script that uses shift.

set: Initializes Command Line Arguments

When you call the set builtin with one or more arguments, it assigns the values of the arguments to the positional parameters, starting with $1. The following script uses set to assign values to the positional parameters $1, $2, and $3:

$ cat set_it set this is it echo $3 $2 $1 $ set_it it is this


Combining command substitution (page 334) with the set builtin is a convenient way to get standard output of a command in a form that can be easily manipulated in a shell script. The following script shows how to use date and set to provide the date in a useful format. The first command shows the output of date. Then cat displays the contents of the dateset script. The first command in this script uses command substitution to set the positional parameters to the output of the date utility. The next command, echo $*, displays all positional parameters resulting from the previous set. Subsequent commands display the values of parameters $1, $2, $3, and $4. The final command displays the date in a format you can use in a letter or report:

$ date Wed Jan  5 23:39:18 PST 2005 $ cat dateset set $(date) echo $* echo echo "Argument 1: $1" echo "Argument 2: $2" echo "Argument 3: $3" echo "Argument 6: $6" echo echo "$2 $3, $6" $ dateset Wed Jan 5 23:39:25 PST 2005 Argument 1: Wed Argument 2: Jan Argument 3: 5 Argument 6: 2005 Jan 5, 2005


You can also use the +format argument to date to modify the format of its output.

When used without any arguments, set displays a list of the shell variables that are set, including user-created variables and keyword variables. Under bash, this list is the same as that displayed by declare and typeset when they are called without any arguments.

The set builtin also accepts options that let you customize the behavior of the shell. For more information refer to "set ±o: Turns Shell Features On and Off" on page 325.

Expanding Null and Unset Variables

The expression ${name} (or just $name if it is not ambiguous) expands to the value of the name variable. If name is null or not set, bash expands ${name} to a null string. The Bourne Again Shell provides the following alternatives to accepting the expanded null string as the value of the variable:

  • Use a default value for the variable.

  • Use a default value and assign that value to the variable.

  • Display an error.

You can choose one of these alternatives by using a modifier with the variable name. In addition, you can use set o nounset (page 327) to cause bash to display an error and exit from a script whenever an unset variable is referenced.

: Uses a Default Value

The : modifier uses a default value in place of a null or unset variable while allowing a nonnull variable to represent itself:


${name:default}

The shell interprets : as "If name is null or unset, expand default and use the expanded value in place of name; else use name." The following command lists the contents of the directory named by the LIT variable. If LIT is null or unset, it lists the contents of /home/alex/literature:

$ ls ${LIT:-/home/alex/literature}


The default can itself have variable references that are expanded:

$ ls ${LIT:-$HOME/literature}


:= Assigns a Default Value

The : modifier does not change the value of a variable. You may want to change the value of a null or unset variable to its default in a script, however. You can do so with the := modifier:


${name:=default}

The shell expands the expression ${name:=default} in the same manner as it expands ${name:default} but also sets the value of name to the expanded value of default. If a script contains a line such as the following and LIT is unset or null at the time this line is executed, LIT is assigned the value /home/alex/literature:

$ ls ${LIT:=/home/alex/literature}


: builtin


Shell scripts frequently start with the : (colon) builtin followed on the same line by the := expansion modifier to set any variables that may be null or unset. The : builtin evaluates each token in the remainder of the command line but does not execute any commands. Without the leading colon (:), the shell evaluates and attempts to execute the "command" that results from the evaluation.

Use the following syntax to set a default for a null or unset variable in a shell script (there is a SPACE following the first colon):


:${name:=default}

When a script needs a directory for temporary files and uses the value of TEMPDIR for the name of this directory, the following line makes TEMPDIR default to /tmp:

: ${TEMPDIR:=/tmp}


:? Displays an Error Message

Sometimes a script needs the value of a variable but you cannot supply a reasonable default at the time you write the script. If the variable is null or unset, the :? modifier causes the script to display an error message and terminate with an exit status of 1:


${name:?message}

You must quote message if it contains SPACEs. If you omit message, the shell displays the default error message (parameter null or not set). Interactive shells do not exit when you use :?. In the following command, TESTDIR is not set so the shell displays on standard error the expanded value of the string following :?. In this case the string includes command substitution for date, with the %T format being followed by the string error, variable not set.

cd ${TESTDIR:?$(date +%T) error, variable not set.} bash: TESTDIR: 16:16:14 error, variable not set.





A Practical Guide to Red Hat Linux
A Practical Guide to Red HatВ® LinuxВ®: Fedoraв„ў Core and Red Hat Enterprise Linux (3rd Edition)
ISBN: 0132280272
EAN: 2147483647
Year: 2006
Pages: 383

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net