shift

Luckily, the shell provides a way around this problem. The command shift performs the function of:

1=$2
2=$3
...

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

for every argument, regardless of how many there are. If you supply a numeric argument to shift, it will shift the arguments that many times over; for example, shift 3 has this effect:

1=$4
2=$5
...

This leads immediately to some code that handles a single option (call it -o) and arbitrarily many arguments:

if [ $1 = -o ]; then
    process the -o option  
    shift
fi
normal processing of arguments...

After the if construct, $1, $2, etc., are set to the correct arguments.

We can use shift together with the programming features we have seen so far to implement simple option schemes. However, we will need additional help when things get more complex. The getopts built-in command, which we will introduce later, provides this help.

shift by itself gives us enough power to implement the - N option to the highest script we saw in Chapter 4 (Task 4-1). Recall that this script takes an input file that lists artists and the number of albums you have by them. It sorts the list and prints out the N highest numbers, in descending order. The code that does the actual data processing is:

filename=$1
howmany=${2:-10}
sort -nr $filename | head -$howmany

Our original syntax for calling this script was highest filename [- N ], where N defaults to 10 if omitted. Let's change this to a more conventional UNIX syntax, in which options are given before arguments: highest [- N ] filename. Here is how we would write the script with this syntax:

if [ -n "$(echo $1 | grep '^-[0-9][0-9]*$')" ]; then
    howmany=$1
    shift
elif [ -n "$(echo $1 | grep '^-')" ]; then
    print 'usage: highest [-N] filename'
    exit 1
else
    howmany="-10"
fi
    
filename=$1
sort -nr $filename | head $howmany

This uses the grep search utility to test if $1 matches the appropriate pattern. To do this we provide the regular expression ^-[0-9][0-9]*$ to grep, which is interpreted as "an initial dash followed by a digit, optionally followed by one or more digits." If a match is found then grep will return the match and the test will be true, otherwise grep will return nothing and processing will pass to the elif test. Notice that we have enclosed the regular expression in single quotes to stop the shell from interpreting the $ and *, and pass them through to grep unmodified.

If $1 doesn't match, we test to see if it's an option at all, i.e., if it matches the pattern - followed by anything else. If it does, then it's invalid; we print an error message and exit with error status. If we reach the final (else) case, we assume that $1 is a filename and treat it as such in the ensuing code. The rest of the script processes the data as before.

We can extend what we have learned so far to a general technique for handling multiple options. For the sake of concreteness, assume that our script is called alice and we want to handle the options -a, -b, and -c:

while [ -n "$(echo $1 | grep '-')" ]; do
    case $1 in
        -a ) process option -a 
               ;;
        -b ) process option -b 
               ;;
        -c ) process option -c 
               ;;
        *  ) echo 'usage: alice [-a] [-b] [-c] args...'
             exit 1
    esac
    shift
done
normal processing of arguments...

This code checks $1 repeatedly as long as it starts with a dash (-). Then the case construct runs the appropriate code depending on which option $1 is. If the option is invalid—i.e., if it starts with a dash but isn't -a, -b, or -c—then the script prints a usage message and returns with an error exit status.

After each option is processed, the arguments are shifted over. The result is that the positional parameters are set to the actual arguments when the while loop finishes.

Notice that this code is capable of handling options of arbitrary length, not just one letter (e.g., -adventure instead of -a).