Although shell programming techniques apply to all shells , generally speaking, there are some differences exist between the C shell and the KornShell. If you are using the C shell, I want you to get off to a quick start, so I'll cover the basics of C shell programming in this section. I'll cover each shell programming technique briefly and use basic examples to help reinforce each technique. In all of the following shell programs, any line beginning with a " # " is a comment. This is true except for the very first line of a shell program, in which the shell the script is written for is executed. In all of the following programs, the C shell is executed with #!/bin/csh , which is the path of the C shell on the Solaris system used in the examples. Command SubstitutionThe shell variables earlier covered can be used to save the output from a command. You can then use these variables when issuing other commands. The following shell program executes the date command and saves the results in the variable d . The variable d is then used within the echo command in the program cdate : #!/bin/csh # program "today" that provides the date set d='date +%x' echo "Today's date is $d" When we run cdate , the following is produced: martyp $ cdate Today's date is 06/01/00 martyp $ The "+%x" in the above example produces the current date. Command substitution of this type is used in several upcoming shell scripts. Reading User InputTwo common methods help you read user input to shell programs. The first is to prompt the user for information, and the second is to provide arguments to shell programs. To begin, I'll cover prompting a user for information. A character, word, or sentence can be read into a variable. The following example first shows prompting the user for a word, and then a sentence : #!/bin/csh echo "Please enter your name:" set name = $< echo "hello, $name" echo "Please enter your favorite quote:" set quote = $< echo "Your favorite quote is:" echo $quote Here is an example of running this program: martyp $ userinput Please enter your name: Marty hello, Marty Please enter your favorite quote: Creating is the essence of life. Your favorite quote is: Creating is the essence of life. martyp $ Using this technique, you can prompt a user for information in a shell program. This technique is used in an upcoming program. You can also enter command-line arguments. When you type the name of the shell script, you can supply arguments that are saved in the variables $1 through $9 . The first ten words on the command line are directly accessible in the shell program using the special variables $0 - $9 . This is how they work:
If you are not sure how many command-line arguments you may get when your program is run, there are two other variables that can help:
The variable $* is commonly used with the for loop (soon to be explained) to process shell script command lines with any number of arguments. The following script changes to the specified directory ( $1 ) and searches for the specified pattern ( $2 ) in the specified file ( $3 ): #!/bin/csh # search # Usage: search directory pattern file echo " " cd # change to search dir and grep -n "" # search for in echo " " # print line endif
Here is an example of the search program: martyp $ search /home/martyp/shellprogs awk ifstat 12:# as one command so it can be easily piped to awk. 18:awk 'BEGIN { printf "%10s%10s%10s%10s%10s\n", "ipkts", 38:' # End of the awk program. martyp $ In this example, we run search in the directory /home/martyp/shellprogs , looking for the pattern awk in the file ifstat . The result of this search produces three lines in the file ifstat , in which awk appears. These are lines number 12, 18, and 38. In the next section, we'll expand this program somewhat to include testing and branching. Testing and BranchingThere are many kinds of decision-making that your shell programs can perform. if provides the flexibility to make decisions and take the appropriate action. Let's expand the search script to verify that three arguments have been provided: #!/bin/csh # search # Usage: search directory pattern files if ($#argv != 3) then # if < 3 args provided echo "Usage: search directory pattern files" # then print Usage else echo " " # else print line and cd # change to search dir grep -n "" # search for in echo " " # print line endif This program is called search1 . We run this program using the same arguments as we did with the search program; however, search1 is enhanced to provide a usage message if we don't provide arguments when we run it. The following example shows running search1 : martyp $ search1 Usage: search directory pattern files martyp $ search1 /home/martyp/shellprogs awk llsum 12:# drwxrwxrwx 2 gerry aec 24 Mar 21 18:25 awk_ex 15:# awk field numbers: 18:awk ' BEGIN { x=i=0; printf "%-16s%-10s%8s%8s\n",\ martyp $
Here are four commonly used forms of if :
There are many operators that can be used in the C shell to compare integer values, such as the < used in the previous example. Here is a list of operators:
LoopingThe C shell supports a number of techniques to support looping, including:
The format of the foreach loop is The following example uses a foreach loop to test whether or not the systems in the /etc/ hosts file are connected to the local host. #!/bin/csh #Program name: csh_hostck #This program will test connectivity to all other hosts in #your network listed in your /etc/hosts file. # It uses the awk command to get the names from the hosts file #and the ping command to check connectivity. #Note that we use /bin/echo because csh echo doesn't support #escape chars like \t or \c which are used in the #foreach loop. #Any line in /etc/hosts that starts with a number represents #a host entry. Anything else is a comment or a blank line. #Find all lines in /etc/hosts that start with a number and #print the second field (the hostname). set hosts='awk '/^[1-9]/ { print }' /etc/hosts' # grave on outside, single quote on inside /bin/echo "Remote host connection status:" foreach sys ($hosts) /bin/echo "$sys - \c" # send one 64 byte packet and look for # the"is alive" message in # the output that indicates success. # messages vary between UNIX variants. ping $sys 64 1 grep "is alive" > /dev/null if ( $status == 0 ) then echo "OK" else echo "DID NOT RESPOND" endif end
The hosts file on this system has three entries: the localhost, the LAN interface, and a DNS system. When we run the program in the following example, we expect to see a result for the testing of all three entries: martyp $ csh_hostck Remote host connection status: localhost - OK sunsys - OK dnssrv1 - OK martyp $ All three entries in the hosts file have been evaluated with ping and produce a status of OK . When hardcoding information into scripts, such as the path of ping and the result you get from the ping command, please keep in mind that these may vary among different UNIX variants. One of the reasons you want to liberally comment your shell programs is to make them easy to modify under such circumstances. You could use the while loop to execute commands for some number of iterations. The while loop is in the following format:
#!/bin/csh # program to run netstat at every specified interval # Usage: netcheck interval set limit=9 # set limit on number times # to run netstat echo " " netstat -i grep Name # print netstat line with headings set count=0 while ($count<$limit) # if limit hasn't reached # limit run netstat netstat -i grep le0 sleep # sleep for interval # specified on command line @ count++ # increment limit end echo "count has reached $limit, run netcheck again to see le0 status" Here is an example run of the netcheck program: martyp $ netcheck 3 Name Mtu Net/Dest Address Ipkts Ierrs Opkts Oerrs Collis Queue le0 1500 sunsys sunsys 314374989 0 17252200 52135 7580906 le0 1500 sunsys sunsys 314375038 0 17252203 52135 7580906 le0 1500 sunsys sunsys 314375114 0 17252206 52135 7580906 le0 1500 sunsys sunsys 314375185 0 17252209 52135 7580906 le0 1500 sunsys sunsys 314375257 0 17252212 52135 7580906 le0 1500 sunsys sunsys 314375332 0 17252215 52135 7580906 le0 1500 sunsys sunsys 314375444 0 17252218 52135 7580906 le0 1500 sunsys sunsys 314375508 0 17252221 52135 7580906 le0 1500 sunsys sunsys 314375588 0 17252224 52135 7580906 count has reached 9, run netcheck again to see le0 status martyp $
This program increments the expression with the following:
If the expression is true, then the command(s) will execute. The @count++ is an assignment operator in the form of:
In this case, the variable is first assigned with "=" and is later auto incremented (++). There are a number of operations that can be performed on the variable, as described in Table 28-2: Table 28-2. Assignment Operators
There are also comparison operators, such as the "<" used in the example, as well as arithmetic, bitwise, and logical operators. As you craft more and more shell scripts, you will want to use all these operators. There are a set of test conditions related to files that are useful when writing shell scripts that use files. Using the format - operator filename , you can use the tests in Table 28-3. Table 28-3. Operator File Name Tests
The following program, called filetest, uses these operators to test the file .profile . Because .profile is not executable, of zero length, or a directory, I would expect filetest to find these false. Here is a long listing of .profile : martyp $ ls -al .profile -rw-r--r-- 1 martyp staff 594 May 21 09:29 ../.profile martyp $ Here is a listing of the shell script filetest : #!/bin/csh # Program to test file if (-e ) then echo " exists" else echo " does not exist" endif if (-z ) then echo " is zero length" else echo " is not zero length" endif if (-f ) then echo " is a file" else echo " is not a file" endif if (-d ) then echo " is a directory" else echo " is not a directory" endif if (-o ) then echo "you own " else echo "you don't own " endif if (-r ) then echo " is readable" else echo " is not readable" endif if (-w ) then echo " is writable" else echo " is not writable" endif if (-x ) then echo " is executable" else echo " is not executable" endif This is a somewhat extreme example of testing a file; however, I wanted to include many of the file tests. Here is the output of filetest using .profile as input: martyp $ filetest /home/martyp/.profile /home/martyp/.profile exists /home/martyp/.profile is not zero length /home/martyp/.profile is a file /home/martyp/.profile is not a directory you own /home/martyp/.profile /home/martyp/.profile is readable /home/martyp/.profile is writable /home/martyp/.profile is not executable martyp $ The result of having run filetest on .profile produces the file test results that we expect. The next section covers a way of making decisions with switch . Decision Making with switchYou can use switch to make decisions within a shell program. You can use switch to test command-line arguments or interactive input to shell programs as shown in the upcoming example. If, for example, you wanted to create a menu in a shell program and you needed to determine which option a user selected when running this shell program, you can use switch . The syntax of switch looks like the following: switch (pattern_to_match) case pattern1 commands breaksw case pattern2 commands breaksw case pattern 3 commands breaksw default commands breaksw endsw pattern_to_match is the user input that you are testing, and if it is equal to pattern1, then the commands under pattern1 are executed. If pattern_to_match and pattern2 are the same, then the commands under pattern2 will be executed, and so on. If no match occurs between pattern_to_match and one of the case statement patterns, then the default is executed. The following program allows you to pick from between two scripts on its menu. These are the two shell programs we crafted earlier in this chapter. You can expand this script to include as many of your programs as you wish. This example uses switch : #!/bin/csh # Program pickscript to run some of # the C shell scripts we've created # Usage: pickscript echo " ---------------------------------------------" echo " Sys Admin Menu " echo "----------------------------------------------" echo " " echo " 1 netcheck for network interface " echo " " echo " 2 hostck to check connection " echo " to hosts in /etc/hosts " echo " " echo " ---------------------------------------------" echo " " echo " Please enter your selection -> \c" set pick = $< # read input which is number of script echo " " switch ($pick) # and assign to variable pick case 1 # if 1 was selected execute this $HOME/cshscripts/netcheck 5 breaksw case 2 # if 2 was selected, execute this $HOME/cshscripts/hostck breaksw default echo "Please select 1 or 2 next time" breaksw endsw This program allows us to select from between two scripts to run. Let's take a look at an example of running this program: martyp $ pickscript --------------------------------------------- Sys Admin Menu ---------------------------------------------- 1 netcheck for network interface 2 hostck to check connection to hosts in /etc/hosts --------------------------------------------- Please enter your selection 1 Name Mtu Net/Dest Address Ipkts Ierrs Opkts Oerrs Collis Queue le0 1500 sunsys sunsys 314996747 0 17261251 52135 7580952 le0 1500 sunsys sunsys 314996862 0 17261256 52135 7580952 le0 1500 sunsys sunsys 314997189 0 17261266 52135 7580952 le0 1500 sunsys sunsys 314997319 0 17261269 52135 7580952 le0 1500 sunsys sunsys 314997420 0 17261272 52135 7580952 le0 1500 sunsys sunsys 314997630 0 17261275 52135 7580952 le0 1500 sunsys sunsys 314997774 0 17261278 52135 7580952 le0 1500 sunsys sunsys 314997904 0 17261281 52135 7580952 le0 1500 sunsys sunsys 314998020 0 17261284 52135 7580952 count has reached 9, run netcheck again to see lan0 status martyp $ We selected option 1 when we ran pickscript . You can use this program as the basis for running the many shell programs you may write. Debugging C Shell ProgramsWhen you begin C shell programming, you'll probably make a lot of simple syntax-related errors. Using the -n option to csh , you can have the C shell check the syntax of your program without executing it. I also use the -v option to produce a verbose output. This can sometimes lead to too much information, so I start with -v and if there is too much feedback results, I eliminate it. The following example is the earlier search1 program, which includes a check that three arguments have been provided. When checking to see that $#argv is equal to 3, I left off the right parenthesis. Here is the listing of the program and a syntax check showing the error: martyp $ cat search1 #!/bin/csh # search # Usage: search directory pattern files if ($#argv !3 3 then # if < 3 args provided echo "Usage: search directory pattern files" # then print Usage else echo " " # else print line and cd # change to search dir grep -n "" # search for in echo " " # print line endif martyp $ csh -nv search1 if ( $#argv != 3 then Too many ('s martyp $ The csh -nv has performed a syntax check with verbose output. First, the line in question is printed and then an error message that tells you what is wrong with the line. In this case, it is clear that I have left off a right parenthesis. After fixing the problem, I can run the program with the -x , which causes all commands to be echoed immediately before execution. The following example shows a run of the search program: martyp $ csh -xv search1 shellprogs grep csh_hostck if ( $#argv != 3 ) then if ( 3 != 3 ) then echo " " echo cd cd /home/martyp/shellprogs grep -n "" grep -n grep csh_hostck 25: /usr/sbin/ping $sys 64 1 grep "is alive" > /dev/null echo " " echo endif endif martyp $
I would recommend performing the syntax check ( -n ) with a new shell program, and then echo all commands with the -x option only if you get unexpected results when you run the program. The debugging options will surely help you at some point when you run into problems with the shell programs you craft. How Long Does It Take?You can use the time command to see a report of the amount of time your shell program takes to run. The output of time is different for many UNIX variants. You may want to view the manual page for time to see what output you can expect. A typical output of time when in csh is shown in Figure 28-5: Figure 28-5. time Example (different among UNIX variants)
Because some of the scripts you write may consume a substantial amount of system resources, you may want to consider investigating some of the job-control capabilities of the C shell. The simplest job control you can use is to run scripts in the background so that the priority of the script is low. By issuing the script name followed by the & (ampersand), you will run the script in the background. If you run several scripts in the background, you can get the status of these by issuing the jobs command. This is a more advanced C shell topic, but depending on the level of complexity of scripts you write, you may want to look into job control. |