Example of a Korn shell script

This topic provides a quick tutorial in the Korn shell.

Let's look at a script that searches C source and header files in the current directory tree for a string passed on the command line:

#!/bin/sh
#
# tfind:
# script to look for strings in various files and dump to less

case $# in
1)
    find . -name '*.[ch]' | xargs grep $1 | less
    exit 0   # good status
esac

echo "Use tfind stuff_to_find                               "
echo "      where : stuff_to_find = search string           "
echo "                                                      "
echo "e.g. tfind console_state looks through all files in   "    
echo "     the current directory and below and displays all "
echo "     instances of console_state."
exit 1    # bad status

As described above, the first line identifies the program, /bin/sh, to run to interpret the script. The next few lines are comments that describe what the script does. Then we see:

case $# in
1)
  ...
esac

The case ... in is a shell builtin command, one of the branching structures provided by the Korn shell, and is equivalent to the C switch statement.

The $# is a shell variable. When you refer to a variable in a shell, put a $ before its name to tell the shell that it's a variable rather than a literal string. The shell variable, $#, is a special variable that represents the number of command-line arguments to the script.

The 1) is a possible value for the case, the equivalent of the C case statement. This code checks to see if you've passed exactly one parameter to the shell.

The esac line completes and ends the case statement. Both the if and case commands use the command's name reversed to represent the end of the branching structure.

Inside the case we find:

find . -name '*.[ch]' | xargs grep $1 | less

This line does the bulk of the work, and breaks down into these pieces:

  • find . -name '*.[ch]'
  • xargs grep $1
  • less

which are joined by the | or pipe character. A pipe is one of the most powerful things in the shell; it takes the output of the program on the left, and makes it the input of the program to its right. The pipe lets you build complex operations from simpler building blocks. For more information, see Redirecting input and output in Using the Command Line.

The first piece, find . -name '*.[ch]', uses another powerful and commonly used command. Most filesystems are recursive through a hierarchy of directories, and find is a utility that descends through the hierarchy of directories recursively. In this case, it searches for files that end in either .c or .h — that is, C source or header files — and prints out their names.

The filename wildcards are wrapped in single quotes (') because they're special characters to the shell. Without the quotes, the shell would expand the wildcards in the current directory, but we want find to evaluate them, so we prevent the shell from evaluating them by quoting them. For more information, see Quoting special characters in Using the Command Line.

The next piece, xargs grep $1, does a couple of things:

  • grep is a file-contents search utility. It searches the files given on its command line for the first argument. The $1 is another special variable in the shell that represents the first argument we passed to the shell script (i.e. the string we're looking for).
  • xargs is a utility that takes its input and turns it into command-line parameters for some other command that you give it. Here, it takes the list of files from find and makes them command-line arguments to grep. In this case, we're using xargs primarily for efficiency; we could do something similar with just find:
    find . -name '*.[ch]' -exec grep $i {} | less
      
    

    which loads and runs the grep program for every file found. The command that we actually used:

    find . -name '*.[ch]' | xargs grep $1 | less
      
    

    runs grep only when xargs has accumulated enough files to fill a command line, generally resulting in far fewer invocations of grep and a more efficient script.

The final piece, less , is an output pager. The entire command may generate a lot of output that might scroll off the terminal, so less presents this to you a page at a time, with the ability to move backwards and forwards through the data.

The case statement also includes the following after the find command:

exit 0   # good status

This returns a value of 0 from this script. In shell programming, zero means true or success, and anything nonzero means false or failure. (This is the opposite of the meanings in the C language.)

The final block:

echo "Use tfind stuff_to_find                               "
echo "      where : stuff_to_find = search string           "
echo "                                                      "
echo "e.g. tfind console_state looks through all files in   "    
echo "     the current directory and below and displays all "
echo "     instances of console_state."
exit 1    # bad status

is just a bit of help; if you pass incorrect arguments to the script, it prints a description of how to use it, and then returns a failure code.