5.4. select

 < Day Day Up > 

All of the flow-control constructs we have seen so far are also available in the Bourne shell, and the C shell has equivalents with different syntax. Our next construct, select, is available only in the Korn shell and bash;[11] moreover, it has no analogy in conventional programming languages.

[11] select is not available in bash versions prior to 1.14.

select allows you to generate simple menus easily. It has concise syntax, but it does quite a lot of work. The syntax is:

select name  [in  list ]  do     statements that can use  $name...  done

This is the same syntax as for except for the keyword select. And like for, you can omit the in list and it will default to "$@", i.e., the list of quoted command-line arguments. Here is what select does:

  1. Generates a menu of each item in list, formatted with numbers for each choice

  2. Prompts the user for a number

  3. Stores the selected choice in the variable name and the selected number in the built-in variable REPLY

  4. Executes the statements in the body

  5. Repeats the process forever (but see below for how to exit)

Here is a task that adds another command to our pushd and popd utilities.

Task 5-5

Write a function that allows the user to select a directory from a list of directories currently in the pushd directory stack. The selected directory is moved to the front of the stack and becomes the current working directory.


The display and selection of directories is best handled by using select. We can start off with something along the lines of:[12]

[12] Versions of bash prior to 1.14.3 have a serious bug with select. These versions will crash if the select list is empty. In this case, surround selects with a test for a null list.

selectd ( ) {     PS3='directory? '     select selection in $DIR_STACK; do         if [ $selection ]; then             #statements that manipulate the stack...             break         else             echo 'invalid selection.'         fi     done }

If you type DIR_STACK="/usr /home /bin" and execute this function, you'll see:

1) /usr 2) /home 3) /bin directory?

The built-in shell variable PS3 contains the prompt string that select uses; its default value is the not particularly useful "#?". So the first line of the above code sets it to a more relevant value.

The select statement constructs the menu from the list of choices. If the user enters a valid number (from 1 to the number of directories), then the variable selection is set to the corresponding value; otherwise it is null. (If the user just presses RETURN, the shell prints the menu again.)

The code in the loop body checks if selection is non-null. If so, it executes the statements we will add in a short while; then the break statement exits the select loop. If selection is null, the code prints an error message and repeats the menu and prompt.

The break statement is the usual way of exiting a select loop. Actually (like its analog in Java and C), it can be used to exit any surrounding control structure we've seen so far (except case, where the double semicolons act like break) as well as the while and until we will see soon. We haven't introduced break until now because it is considered bad coding style to use it to exit a loop. However, it can make code easier to read if used judiciously. break is necessary for exiting select when the user makes a valid choice.[13]

[13] A user can also type CTRL-D (for end-of-input) to get out of a select loop. This gives the user a uniform way of exiting, but it doesn't help the shell programmer much.

Now we'll add the missing pieces to the code:

selectd ( ) {     PS3='directory? '     dirstack=" $DIR_STACK "           select selection in $dirstack; do         if [ $selection ]; then             DIR_STACK="$selection${dirstack%% $selection *}"             DIR_STACK="$DIR_STACK ${dirstack##* $selection }"             DIR_STACK=${DIR_STACK% }             cd $selection             break         else             echo 'invalid selection.'         fi     done }

The first two lines initialize environment variables. dirstack is a copy of DIR_STACK with spaces appended at the beginning and end so that each directory in the list is of the form space directory space. This form simplifies the code when we come to manipulating the directory stack.

The select and if statements are the same as in our initial function. The new code inside the if uses bash's pattern-matching capability to manipulate the directory stack.

The first statement sets DIR_STACK to selection, followed by dirstack with everything from selection to the end of the list removed. The second statement adds everything in the list from the directory following selection to the end of DIR_STACK. The next line removes the trailing space that was appended at the start. To complete the operation, a cd is performed to the new directory, followed by a break to exit the select code.

As an example of the list manipulation performed in this function, consider a DIR_STACK set to /home /bin /usr2. In this case, dirstack would become /home /bin /usr2. Typing selectd would result in:

$ selectd 1) /home 2) /bin 3) /usr2 directory?

After selecting /bin from the list, the first statement inside the if section sets DIR_STACK to /bin followed by dirstack with everything from /bin onwards removed, i.e., /home.

The second statement then takes DIR_STACK and appends everything in dirstack following /bin (i.e., /usr2) to it. The value of DIR_STACK becomes /bin /home /usr2. The trailing space is removed in the next line.

     < Day Day Up > 


    Learning the bash Shell
    Learning the bash Shell: Unix Shell Programming (In a Nutshell (OReilly))
    ISBN: 0596009658
    EAN: 2147483647
    Year: 2005
    Pages: 139

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