Chapter 9. 'Round and 'Round She Goes

In this chapter you'll learn how to set up program loops. These loops will enable you to execute repeatedly a set of commands either a specified number of times or until some condition is met. The three built-in looping commands are

  • for

  • while

  • until

You'll learn about each one of these loops in separate sections of this chapter.

The for Command

The for command is used to execute a set of commands a specified number of times. Its basic format is as shown:

for var in word1 word2 ... wordn
do
          command
          command
          ...
done

The commands enclosed between the do and the done form what's known as the body of the loop. These commands are executed for as many words as you have listed after the in. When the loop is executed, the first word, word1, is assigned to the variable var, and the body of the loop is then executed. Next, the second word in the list, word2, is assigned to var, and the body of the loop is executed. This process continues with successive words in the list being assigned to var and the commands in the loop body being executed until the last word in the list, wordn, is assigned to var and the body of the loop executed. At that point, no words are left in the list, and execution of the for command is then finished. Execution then continues with the command that immediately follows the done. So if there are n words listed after the in, the body of the loop will have been executed a total of n times after the loop has finished.

Here's a loop that will be executed a total of three times:

for i in 1 2 3
do
       echo $i
done

To try it out, you can type this in directly at the terminal, just like any other shell command:

$ for i in 1 2 3
> do
>         echo $i
> done
1
2
3
$

While the shell is waiting for the done to be typed to close off the for command, it displays your secondary command prompt. When it gets the done, the shell then proceeds to execute the loop. Because three words are listed after the in (1, 2, and 3), the body of the loop—in this case a single echo command—will be executed a total of three times.

The first time through the loop, the first word in the list, 1, is assigned to the variable i. Then the body of the loop is executed. This displays the value of i at the terminal. Then the next word in the list, 2, is assigned to i and the echo command re-executed, resulting in the display of 2 at the terminal. The third word in the list, 3, is assigned to i the third time through the loop and the echo command executed. This results in 3 being displayed at the terminal. At that point, no more words are left in the list, so execution of the for command is then complete, and the shell displays your command prompt to let you know it's done.

Recall the run program from Chapter 7, “Passing Arguments,” that enabled you to run a file through tbl, nroff, and lp:

$ cat run
tbl $1 | nroff -mm -Tlp | lp
$

If you wanted to run the files memo1 through memo4 through this program, you could type the following at the terminal:

$ for file in memo1 memo2 memo3 memo4
> do
>         run $file
> done
request id is laser1-33 (standard input)
request id is laser1-34 (standard input)
request id is laser1-35 (standard input)
request id is laser1-36 (standard input)
$

The four words memo1, memo2, memo3, and memo4 will be assigned to the variable file in order, and the run program will be executed with the value of this variable as the argument. Execution will be just as if you typed in the four commands:

$ run memo1
request id is laser1-33 (standard input)
$ run memo2
request id is laser1-34 (standard input)
$ run memo3
request id is laser1-35 (standard input)
$ run memo4
request id is laser1-36 (standard input)
$

Incidentally, the shell permits filename substitution in the list of words in the for, meaning that the previous loop could have also been written this way:

for file in memo[1-4]
do
        run $file
done

And if you wanted to run all the files in your current directory through run, you could type

for file in *
do
        run $file
done

If the file filelist contains a list of the files that you want to run through run, you can type

files=$(cat filelist)

for file in $files
do
        run $file
done

to run each of the files, or, more succinctly,

for file in $(cat filelist)
do
        run $file
done

If you found that you were using the run program often to process several files at once, you could go inside the run program and modify it to allow any number of files to be passed as arguments to the program.

$ cat run
#
# process files through nroff -- version 2
#
for file in $*
do
        tbl $file | nroff -rom -Tlp | lp
done
$

Recall that the special shell variable $* stands for all the arguments typed on the command line. So if you executed the new version of run by typing

run memo1 memo2 memo3 memo4

the $* in the for's list would be replaced by the four arguments memo1, memo2, memo3, and memo4. Of course, you could also type

run memo[1-4]

to achieve the same results.

The $@ Variable

While we're on the subject of $*, let's look at it in a bit more detail. We'll write a program called args that displays all the arguments typed on the command line, one per line.

$ cat args
echo Number of arguments passed is $#

for arg in $*
do
       echo $arg
done
$

Now to try it:

$ args a b c
Number of arguments passed is 3
a
b
c
$ args 'a b' c
Number of arguments passed is 2
a
b
c
$

In the second case, even though a b was passed as a single argument to args, the $* in the for command was replaced by the shell with a b c, which is three words. Thus the loop was executed three times.

Whereas the shell replaces the value of $* with $1, $2, ..., if you instead use the special shell variable "$@" it will be replaced with "$1", "$2", ... . The double quotes are necessary around $@ because without them this variable behaves just like $*.

Go back to the args program and replace the $* with "$@":

$ cat args
echo Number of arguments passed is $#

for arg in "$@"
do
       echo $arg
done
$

Now try it:

$ args a b c
Number of arguments passed is 3
a
b
c
$ args 'a b' c
Number of arguments passed is 2
a b
c
$ args                          Try it with no arguments
Number of arguments passed is 0
$

In the last case, no arguments were passed to the program. So the variable "$@" was replaced by nothing. The net result is that the body of the loop was not executed at all.

The for Without the List

A special notation is recognized by the shell when writing for commands. If you write

for var
do
         command
         command
         ...
done

(note the absence of the in), the shell automatically sequences through all the arguments typed on the command line, just as if you had written

for var in "$@"
do
         command
         command
         ...
done

Here's the third and last version of the args program:

$ cat args
echo Number of arguments passed is $#

for arg
do
        echo $arg
done
$ args a b c
Number of arguments passed is 3
a
b
c
$ args 'a b' c
Number of arguments passed is 2
a b
c
$

The while Command

The second type of looping command to be described in this chapter is the while. The format of this command is

while commandt
do
         command
         command
         ...
done

commandt is executed and its exit status tested. If it's zero, the commands enclosed between the do and done are executed. Then commandt is executed again and its exit status tested. If it's zero, the commands enclosed between the do and done are once again executed. This process continues until commandt returns a nonzero exit status. At that point, execution of the loop is terminated. Execution then proceeds with the command that follows the done.

Note that the commands between the do and done might never be executed if commandt returns a nonzero exit status the first time it's executed.

Here's a program called twhile that simply counts to 5:

$ cat twhile
i=1

while [ "$i" -le 5 ]
do
        echo $i
        i=$((i + 1))
done
$ twhile                 Run it
1
2
3
4
5
$

The variable i is used as the counting variable and is initially set equal to 1. Then the while loop is entered. It continues execution as long as i is less than or equal to 5. Inside the loop, the value of i is displayed at the terminal. Then it is incremented by one.

The while loop is often used in conjunction with the shift command to process a variable number of arguments typed on the command line. The next program, called prargs, prints each of the command-line arguments one per line.

$ cat prargs
#
# Print command line arguments one per line
#

while [ "$#" -ne 0 ]
do
      echo "$1"
      shift
done
$ prargs a b c
a
b
c
$ prargs 'a b' c
a b
c
$ prargs *
addresses
intro
lotsaspaces
names
nu
numbers
phonebook
stat
$ prargs        No arguments
$

While the number of arguments is not equal to zero, the value of $1 is displayed and then a shift executed. Recall that this shifts down the variables (that is, $2 to $1, $3 to $2, and so on) and also decrements $#. When the last argument has been displayed and shifted out, $# will equal zero, at which point execution of the while will be terminated. Note that if no arguments are given to prargs (as was done in the last case), the echo and shift are never executed because $# is equal to zero as soon as the loop is entered.

The until Command

The while command continues execution as long as the command listed after the while returns a zero exit status. The until command is similar to the while, only it continues execution as long as the command that follows the until returns a nonzero exit status. As soon as a zero exit status is returned, the loop is terminated. Here is the general format of the until:

until commandt
do
         command
         command
         ...
done

Like the while, the commands between the do and done might never be executed if commandt returns a zero exit status the first time it's executed.

The until command is useful for writing programs that wait for a particular event to occur. For example, suppose that you want to see whether sandy is logged on because you have to give her something important. You could send her electronic mail, but you know that she usually doesn't get around to reading her mail until late in the day. One approach is to use the on program from Chapter 8, “Decisions, Decisions,” to see whether sandy's logged on:

$ on sandy
sandy is not logged on
$

You could execute this program periodically throughout the day, until sandy eventually logs on, or you could write your own program to continually check until she does. Let's call the program mon and have it take a single argument: the name of the user you want to monitor. Instead of having the program continually check for that user logging on, we'll have it check only once every minute. To do this, you have to know about a command called sleep that suspends execution of a program for a specified number of seconds. So the Unix command (this isn't a shell built-in)

sleep n

suspends execution of the program for n seconds. At the end of that interval, the program resumes execution where it left off—with the command that immediately follows the sleep.

$ cat mon
#
# Wait until a specified user logs on
#

if [ "$#" -ne 1 ]
then
       echo "Usage: mon user"
       exit 1
fi

user="$1"

#
# Check every minute for user logging on
#

until who | grep "^$user " > /dev/null
do
        sleep 60
done

#
# When we reach this point, the user has logged on
#
echo "$user has logged on"
$

After checking that one argument was provided, the program assigns $1 to user. Then an until loop is entered. This loop will be executed until the exit status returned by grep is zero; that is, until the specified user logs on. As long as the user isn't logged on, the body of the loop—the sleep command—is executed. This command suspends execution of the program for one minute (60 seconds). At the end of the minute, the pipeline listed after the until is re-executed and the process repeated.

When the until loop is exited—signaling that the monitored user has logged on—a message is displayed at the terminal to that effect.

$ mon sandy                  Time passes
sandy has logged on
$

Using the program as shown here is not very practical because it ties up your terminal until sandy logs on. A better idea is to run mon in the background so that you can use your terminal for other work:

$ mon sandy &                Run it in the background
[1] 4392                     Job number and process id
$ nroff newmemo              Do other work
   ...
sandy has logged on          Happens sometime later

So now you can do other work and the mon program continues executing in the background until sandy logs on, or until you log off the system.[1]

Because mon only checks once per minute for the user's logging on, it won't hog the system's resources while it's running (an important consideration when submitting programs to the background for execution).

Unfortunately, after the specified user logs on, there's a chance you might miss that one-line message (you may be cating a file and might not even notice it come and go right off your screen). Also if you're editing a file with a screen editor such as vi when the message comes, it may turn your screen into a mess, and you still might miss the message. A better alternative to writing the message to the terminal might be to mail it instead. Actually, you can let the user select his or her preference by adding an option to the program that, if selected, indicates that the message is to be mailed. If the option is not selected, the message can be displayed at the terminal.

In the version of mon that follows, a -m option has been added for this purpose:

$ cat mon
#
# Wait until a specified user logs on -- version 2
#

if [ "$1" = -m ]
then
        mailopt=TRUE
        shift
else
        mailopt=FALSE
fi

if [ "$#" -eq 0  -o  "$#" -gt 1 ]
then
        echo "Usage: mon [-m] user"
        echo"    -m means to be informed by mail"
        exit 1
fi

user="$1"

#
# Check every minute for user logging on
#

until who | grep "^$user " > /dev/null
do
        sleep 60
done

#
# When we reach this point, the user has logged on
#

if [ "$mailopt" = FALSE ]
then
        echo "$user has logged on"
else
        echo "$user has logged on" | mail steve
fi
$

The first test checks to see whether the -m option was supplied. If it was, the characters TRUE are assigned to the variable mailopt, and shift is executed to “shift out” the first argument (moving the name of the user to be monitored to $1 and decrementing $#). If the -m option wasn't specified as the first argument, the characters FALSE are assigned to mailopt.

Execution then proceeds as in the previous version. However, this time when the loop is exited a test is made to see whether the -m option was selected. If it wasn't, the message is written to standard output; otherwise, it's mailed to steve.

$ mon sandy -m
Usage: mon [-m] user
       -m means to be informed by mail
$ mon -m sandy &
[1] 5435
$ vi newmemo                Work continues
  ...
you have mail
$ mail
From steve Wed Aug 28 17:44:46 EDT 2002
sandy has logged on

?d
$

Of course, we could have written mon to accept the -m option as either the first or second argument, but that goes against the recommended command syntax standard, which specifies that all options should precede any other types of arguments on the command line.[2]

Also note that the old version of mon could have been executed as follows:

$ mon sandy | mail steve &
[1] 5522
$

to achieve the same net result as adding the -m option.

Two last points before leaving the discussion of mon: First, you'll probably always want to run this program in the background. It would be nice if mon itself could take care of that. Later you'll see how to do it.

Second, the program always sends mail to steve; not very nice if someone else wants to run it. A better way is to determine the user running the program and then send him or her the mail if the -m option is selected. But how do you do that? One way is to execute the who command with the am i options and get the user name that comes back. This tells you who's logged on to the terminal that the program was run from. You can then use cut to extract the username from who's output and use that name as the recipient of the mail. All this can be done in the last if command of mon if it's changed to read as shown:

if [ "$#" -eq 1 ]
then
       echo "$user has logged on"
else
       runner=$(who am i | cut -c1-8)
       echo "$user has logged on" | mail $runner
fi

Now the program can be run by anyone, and the mail will be properly sent.

More on Loops

Breaking Out of a Loop

Sometimes you may want to make an immediate exit from a loop. To just exit from the loop (and not from the program), you can use the break command, whose format is simply

break

When the break is executed, control is sent immediately out of the loop, where execution then continues as normal with the command that follows the done.

The Unix command true serves no purpose but to return an exit status of zero. The command false also does nothing but return a nonzero exit status. If you write

while true
do
       ...
done

the while loop will theoretically be executed forever because true always returns a zero exit status. By the way, the : command also does nothing but return a zero exit status, so an “infinite” loop can also be set up with

while :
do
       ...
done

Because false always returns a nonzero exit status, the loop

until false
do
       ...
done

will theoretically execute forever.

The break command is often used to exit from these sorts of infinite loops, usually when some error condition or the end of processing is detected:

while true
do
        cmd=$(getcmd)

        if [ "$cmd" = quit ]
        then
                break
        else
                processcmd "$cmd"
        fi
done

Here the while loop will continue to execute the getcmd and processcmd programs until cmd is equal to quit. At that point, the break command will be executed, thus causing the loop to be exited.

If the break command is used in the form

break n

the n innermost loops are immediately exited, so in

for file
do
       ...
       while [ "$count" -lt 10 ]
       do
              ...
              if [ -n "$error" ]
              then
                     break 2
              fi
              ...
       done
       ...
done

both the while and the for loops will be exited if error is nonnull.

Skipping the Remaining Commands in a Loop

The continue command is similar to break, only it doesn't cause the loop to be exited, merely the remaining commands in the loop to be skipped. Execution of the loop then continues as normal. Like the break, an optional number can follow the continue, so

continue n

causes the commands in the innermost n loops to be skipped; but execution of the loops then continues as normal.

for file
do
       if [ ! -e "$file" ]
       then

              echo "$file not found!"
              continue
       fi

       #
       # Process the file
       #

       ...
done

Each value of file is checked to make sure that the file exists. If it doesn't, a message is printed, and further processing of the file is skipped. Execution of the loop then continues with the next value in the list. Note that the preceding example is equivalent to writing

for file
do
       if [ ! -e "$file" ]
       then
              echo "$file not found!"
       else
              #
              # Process the file
              #

              ...
       fi
done

Executing a Loop in the Background

An entire loop can be sent to the background for execution simply by placing an ampersand after the done:

$ for file in memo[1-4]
> do
>         run $file
> done &                     Send it to the background
[1] 9932
$
request id is laser1-85 (standard input)
request id is laser1-87 (standard input)
request id is laser1-88 (standard input)
request id is laser1-92 (standard input)

I/O Redirection on a Loop

You can also perform I/O redirection on the entire loop. Input redirected into the loop applies to all commands in the loop that read their data from standard input. Output redirected from the loop to a file applies to all commands in the loop that write to standard output:

$ for i in 1 2 3 4
> do
>         echo $i
> done > loopout             Redirect loop's output to loopout
$ cat loopout
1
2
3
4
$

You can override redirection of the entire loop's input or output by explicitly redirecting the input and/or output of commands inside the loop. To force input or output of a command to come from or go to the terminal, use the fact that /dev/tty always refers to your terminal. In the following loop, the echo command's output is explicitly redirected to the terminal to override the global output redirection applied to the loop:

for file
do
       echo "Processing file $file" > /dev/tty
       ...
done > output

echo's output is redirected to the terminal while the rest goes to the file output.

Naturally, you can also redirect the standard error output from a loop, simply by tacking on a 2> file after the done:

while [ "$endofdata" -ne TRUE ]
do
          ...
done 2> errors

Here output from all commands in the loop writing to standard error will be redirected to the file errors.

Piping Data Into and Out of a Loop

A command's output can be piped into a loop, and the entire output from a loop can be piped into another command in the expected manner. Here's a highly manufactured example of the output from a for command piped into wc:

$ for i in 1 2 3 4
> do
>         echo $i
> done | wc –l
      4
$

Typing a Loop on One Line

If you find yourself frequently executing loops directly at the terminal, you'll want to use the following shorthand notation to type the entire loop on a single line: Put a semicolon after the last item in the list and one after each command in the loop. Don't put a semicolon after the do.

Following these rules, the loop

for i in 1 2 3 4
do
        echo $i
done

becomes

for i in 1 2 3 4; do echo $i; done

And you can type it in directly this way:

$ for i in 1 2 3 4; do echo $i; done
1
2
3
4
$

The same rules apply to while and until loops.

if commands can also be typed on the same line using a similar format:

$ if [ 1 = 1 ]; then echo yes; fi
yes
$ if [ 1 = 2 ]; then echo yes; else echo no; fi
no
$

Note that no semicolons appear after the then and the else.

The getopts Command

Let's extend our mon program further. We'll add a -t option to it that specifies the time interval, in seconds, to perform the check. Now our mon program takes both -m and -t options. We'll allow it to take these options in any order on the command line, provided that if they are used, they appear before the name of the user that we're monitoring. So valid mon command lines look like this:

mon ann
mon -m ann
mon -t 600 ann
mon -m -t 600 ann
mon -t 600 -m ann

and invalid ones look like this:

mon                          Missing user name
mon -t600 ann                Need a space after -t
mon ann -m                   Options must appear first
mon -t ann                   Missing argument after -t

If you start writing the code to allow this sort of flexibility on the command line, you will soon discover that it can start to get a bit complex. Luckily, the shell provides a built-in command called getopts that exists for the express purpose of processing command-line arguments. The general format of the command is

getopts options variable

The getopts command is designed to be executed inside a loop. Each time through the loop, getopts examines the next command line argument and determines whether it is a valid option. This determination is made by checking to see whether the argument begins with a minus sign and is followed by any single letter contained inside options. If it does, getopts stores the matching option letter inside the specified variable and returns a zero exit status.

If the letter that follows the minus sign is not listed in options, getopts stores a question mark inside variable before returning with a zero exit status. It also writes an error message to standard error.

If no more arguments are left on the command line or if the next argument doesn't begin with a minus sign, getopts returns a nonzero exit status.

Suppose that you want getopts to recognize the options -a, -i, and -r for a command called foo. Your getopts call might look like this:

getopts air option

Here the first argument—air—specifies the three acceptable options to the command, and option specifies the variable that getopts will use as previously described.

The getopts command permits options to be “stacked” together on the command line. This is done by following a single minus sign with one or more consecutive options letters. For example, our foo command can be executed like this:

foo -a -r -i

or like this:

foo -ari

using this stacking feature.

The getopts command also handles the case where an option must be followed by an argument. For example, the new -t option to be added to the mon command requires a following argument. To handle options that take arguments, getopts requires that at least one whitespace character separate the option from the argument. Furthermore, such options cannot be stacked.

To indicate to getopts that an option takes a following argument, you write a colon character after the option letter on the getopts command line. So our mon program, which takes -m and -t options, should call getopts like this:

getopts mt: option

If getopts doesn't find an argument after an option that requires one, it stores a question mark inside the specified variable and writes an error message to standard error. Otherwise, it stores the actual argument inside a special variable called OPTARG.

One final note about getopts: Another special variable called OPTIND is used by the command. This variable is initially set to one and is updated each time getopts returns to reflect the number of the next command-line argument to be processed.

Here is the third version of mon that uses the getopts command to process the command-line arguments. It also incorporates the previously noted change to send mail to the user running the program.

$ cat mon
#
# Wait until a specified user logs on -- version 3
#

# Set up default values

mailopt=FALSE
interval=60

# process command options

while getopts mt: option
do
       case "$option"
       in
              m) mailopt=TRUE;;
              t) interval=$OPTARG;;
             ?) echo "Usage: mon [-m] [-t n] user"
                 echo "   -m means to be informed by mail"
                 echo "   -t means check every n secs."
                 exit 1;;
       esac
done

# Make sure a user name was specified

if [ "$OPTIND" -gt "$#" ]
then
       echo "Missing user name!"
       exit 2
fi

shiftcount=$((OPTIND – 1))
shift $shiftcount
user=$1

#
# Check for user logging on
#

until who | grep "^$user " > /dev/null
do
       sleep $interval
done

#
# When we reach this point, the user has logged on
#

if [ "$mailopt" = FALSE]
then
       echo "$user has logged on"
else
       runner=$(who am i | cut -c1-8)
       echo "$user has logged on" | mail $runner
fi


$ mon -m
Missing user name!
$ mon -x fred                        Illegal option
mon: illegal option -- x
Usage: mon [-m] [-t n] user
   -m means to be informed by mail
   -t means check every n secs.
$ mon -m -t 600 ann &               Check every 10 min. for ann
[1] 5792
$

When the line

mon -m -t 600 ann &

is executed, the following occurs inside the while loop in mon: getopts is executed, and it stores the character m inside the variable option, sets OPTIND to two, and returns a zero exit status. The case command is then executed to determine what was stored inside option. A match on the character m indicates that the “send mail” option was selected, so mailopt is set to TRUE. (Note that the ? inside the case is quoted. This is to remove its special meaning as a pattern-matching character from the shell.)

The second time getopts is executed, getopts stores the character t inside option, stores the next command-line argument (600) inside OPTARG, sets OPTIND to three, and returns a zero exit status. The case command then matches the character t stored inside option. The code associated with that case copies the value of 600 that was stored in OPTARG into the variable interval.

The third time getopts is executed, getopts returns a nonzero exit status, indicating the end of options. The program then checks the value of OPTIND against $# to make sure that the username was typed on the command line. If OPTIND is greater than $#, then no more arguments remain on the command line and the user forgot the username argument. Otherwise, the shift command is executed to move the username argument into $1. The actual number of places to shift is one less than the value of OPTIND.

The rest of the mon program remains as before; the only change is the use of the interval variable to specify the number of seconds to sleep.

Exercises

1:

Modify the prargs program to precede each argument by its number. So typing

prargs a 'b c' d

should give the following output:

1: a
2: b c
3: d

2:

Modify the mon program to also print the tty number that the user logs on to. That is, the output should say

sandy logged onto tty13

if sandy logs on to tty13.

3:

Add a -f option to mon to have it periodically check for the existence of a file (ordinary file or directory) instead of for a user logging on. So typing

mon -f /usr/spool/uucppublic/steve/newmemo &

should cause mon to periodically check for the existence of the indicated file and inform you when it does (by displaying a message or by mail if the -m option is also selected).

4:

Add a -n option to mon that inverts the monitoring function. So

mon -n sandy

checks for sandy logging off the system, and

mon -n -f /tmp/dataout &

periodically checks for the removal of the specified file.

5:

Write a program called collect that runs in the background and counts the number of users logged in at the end of each interval and also the number of processes run during that interval. Allow the interval to be specified with a -t option (see the previous exercise), with the default 10 minutes. Use the fact that the special shell variable $! is set to the process number of the last command executed in the background and that

: &

runs a null command in the background. Also make sure that the program correctly handles the case where the process number loops back around to 1 after the maximum is reached.

So

collect -t 900 > stats &

should start up collect to gather the desired statistics every 15 minutes and write them into the file stats.

6:

Write a shell program called wgrep that searches a file for a given pattern, just as grep does. For each line in the file that matches, print a “window” around the matching line. That is, print the line preceding the match, the matching line, and the line following the match. Be sure to properly handle the special cases where the pattern matches the first line of the file and where the pattern matches the last line of the file.

7:

Modify wgrep to take an optional -w option that specifies the window size; so

wgrep -w 3 UNIX text

should print three lines before and after each line from text that contains the pattern UNIX.

8:

Modify wgrep to take a variable number of filenames as arguments. Precede each output line with the name of the file in which the match occurs (as grep does).



[1] All your processes are automatically terminated when you log off the system. If you want a program to continue executing after you've logged off, you can run it with the nohup command, or schedule it to run with at or from the cron. Consult your Unix User's Manual for more details.

[2] The command syntax standard consists of a set of rules as outlined in the Utility Argument Syntax section of the POSIX standard.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.212.186