Wednesday, October 28, 2009

The set Command








 

 










The set Command



The shell's set command is a dual-purpose command: it's used both to set various shell options as well as to reassign the positional parameters $1, $2, and so forth.



The -x Option



This option turns on trace mode in the shell. It does to the current shell what the command





sh -x ctype a



did for the execution of the ctype program in Chapter 8, "Decisions, Decisions." From the point that the





set -x



command is executed, all subsequently executed commands will be printed to standard error by the shell, after filename, variable, and command substitution and I/O redirection have been performed. The traced commands are preceded by plus signs.





$ x=*

$ set -x Set command trace option

$ echo $x

+ echo add greetings lu rem rolo

add greetings lu rem rolo

$ cmd=wc

+ cmd=wc

$ ls | $cmd -l

+ ls

+ wc -l

5

$



You can turn off trace mode at any time simply by executing set with the +x option:





$ set +x

+ set +x

$ ls | wc �l

5 Back to normal

$



You should note that the trace option is not passed down to subshells. But you can trace a subshell's execution either by running the shell with the -x option followed by the name of the program to be executed, as in





sh -x rolo



or you can insert a set -x command inside the file itself. In fact, you can insert any number of set -x and set +x commands inside your program to turn trace mode on and off as desired.



set with No Arguments



If you don't give any arguments to set, you'll get an alphabetized list of all the variables that exist in your environment, be they local or exported:





$ set Show me all variables

CDPATH=:/users/steve:/usr/spool

EDITOR=/bin/vi

HOME=/users/steve

IFS=



LOGNAME=steve

MAIL=/usr/spool/mail/steve

MAILCHECK=600

PATH=/bin:/usr/bin:/users/steve/bin:.:

PHONEBOOK=/users/steve/phonebook

PS1=$

PS2=>

PWD=/users/steve/misc

SHELL=/usr/bin/sh

TERM=xterm

TMOUT=0

TZ=EST5EDT

cmd=wc

x=*

$



Using set to Reassign Positional Parameters



There is no way to directly assign a value to a positional parameter; for example,





1=100



does not work. These parameters are initially set on execution of the shell program. The only way they may be changed is with the shift or the set commands. If words are given as arguments to set on the command line, those words will be assigned to the positional parameters $1, $2, and so forth. The previous values stored in the positional parameters will be lost forever. So





set a b c



assigns a to $1, b to $2, and c to $3. $# also gets set to 3.





$ set one two three four

$ echo $1:$2:$3:$4

one:two:three:four

$ echo $# This should be 4

4

$ echo $* What does this reference now?

one two three four

$ for arg; do echo $arg; done

one

two

three

four

$



So after execution of the set, everything seems to work consistently: $#, $*, and the for loop without a list.



set is often used in this fashion to "parse" data read from a file or the terminal. Here's a program called words that counts the number of words typed on a line (using the shell's definition of a "word"):





$ cat words

#

# Count words on a line

#



read line

set $line

echo $#

$ words Run it

Here's a line for you to count.

7

$



The program stores the line read in the shell variable line and then executes the command





set $line



This causes each word stored in line to be assigned to the positional parameters. The variable $# is also set to the number of words assigned, which is the number of words on the line.



The -- Option



Try typing in a line to words that begins with a - and see what happens:





$ words

-1 + 5 = 4

words: -1: bad option(s)

$



After the line was read and assigned to line, the command





set $line



was executed. After the shell did its substitution, the command line looked like this:





set -1 + 5 = 4



When set executed, it saw the - and thought that an option was being selected, thus explaining the error message.



Another problem with words occurs if you give it a line consisting entirely of whitespace characters, or if the line is null:





$ words

Just Enter is pressed

CDPATH=.:/users/steve:/usr/spool

EDITOR=/bin/vi

HOME=/users/steve

IFS=



LOGNAME=steve

MAIL=/usr/spool/mail/steve

MAILCHECK=600

PATH=/bin:/usr/bin:/users/steve/bin:.:

PHONEBOOK=/users/steve/phonebook

PS1=$

PS2=>

PWD=/users/steve/misc

SHELL=/usr/bin/sh

TERM=xterm

TMOUT=0

TZ=EST5EDT

cmd=wc

x=*

0

$



To protect against both of these problems occurring, you can use the -- option to set. This tells set not to interpret any subsequent arguments on the command line as options. It also prevents set from displaying all your variables if no other arguments follow, as was the case when you typed a null line.



So the set command in words should be changed to read





set -- $line



With the addition of a while loop and some integer arithmetic, the words program can be easily modified to count the total number of words on standard input, giving you your own version of wc -w:





$ cat words

#

# Count all of the words on standard input

#



count=0

while read line

do

set -- $line

count=$(( count + $# ))

done



echo $count

$



After each line is read, the set command is executed to take advantage of the fact that $# will be assigned the number of words on the line. The -- option is supplied to set just in case any of the lines read begins with a - or consists entirely of whitespace characters.



The value of $# is then added into the variable count, and the next line is read. When the loop is exited, the value of count is displayed. This represents the total number of words read.





$ words < /etc/passwd

567

$ wc -w < /etc/passwd Check against wc

567

$



(Our version is a lot slower than wc because the latter is written in C.)



Here's a quick way to count the number of files in your directory:[1]



[1] This technique may not work on very large directories because you may exceed the limit on the length of the command line (the precise length varies between Unix systems). Working with such directories may cause problems when using filename substitution in other commands as well, such as echo * or for file in *.





$ set *

$ echo $#

8

$



This is much faster than





ls | wc -l



because the first method uses only shell built-in commands. In general, your shell programs run much faster if you try to get as much done as you can using the shell's built-in commands.



Other Options to set



set accepts several other options, each of them enabled by preceding the option with a -, and disabled by preceding it with a +. The -x option that we have described here is perhaps the most commonly used. Others are summarized in Table A.9 in Appendix A.












     

     


    No comments:

    Post a Comment