Korn Shell Programming v2
Korn Shell Programming v2
Programming
Tried and True Programming Techniques to Get You Started Quickly by Ken Gottry
Table of Contents
Background ...................................................................................................................2 Writing a Script Some Basics......................................................................................3 Writing a Simple Script ..................................................................................................4 Run the UNIX Command Interactively ..................................................................4 Create a Shell Script ..................................................................................................4 Make the Shell Script Executable ...........................................................................4 Test the Shell Script.....................................................................................................5 Launching the Script..................................................................................................6 Extending the Simple Script..........................................................................................8 Redirecting stdout......................................................................................................8 Executing sub-commands within the script .........................................................8 Generating Unique File Names...............................................................................9 Structured Programming Techniques ....................................................................9 Writing a for-loop Script...........................................................................................11 Writing a while-loop Script ......................................................................................12 Quick Reference Card................................................................................................14
Background
The command shell is the layer that interacts with the user and communicates with the operating system. When using MS-DOS most people use the command.com shell; however a different shell can be specified via the COMSPEC environment variable. Similarly, each UNIX user must select a command shell to use to communicate to UNIX. When a UNIX account is established the sysadmin selects the users default shell. Normal options are Bourne Shell (/bin/sh), C-Shell (/bin/csh), Korn Shell (/bin/ksh) and Bourne-Again Shell (/bin/bash). While many developers use C-Shell because of its C-like syntax, This is a subjective selection and this article uses the Korn shell exclusively. All of the scripts discussed in this document are written using Korn shell. The syntax will not necessarily work under any other shell. When you execute a shell script from the command line, your default shell is used. If your default shell is Korn, then your scripts execute without syntax errors. But what if you want others to execute your script. You cant rely on the users default shell to ensure your scripts are always run using the Korn shell. So, you use a UNIX feature whereby the first line of a shell script indicates under what shell the script is to be executed. The syntax in Figure 1 forces a script to be run using the Korn shell regardless of what shell the current user is executing.
#!/bin/ksh # your script goes here. All lines starting with # are treated as comments Figure 1 - Force a script to be executed by the Korn shell
Some documentation uses a different command prompt symbol to indicate the current shell as shown in Figure 2. Since this authors favorite shell is the Korn shell, all of the examples in this article use the $-prompt. Since you cannot ensure that your scripts will always be executed using the Korn shell, put #!/bin/ksh as the first line in each scripts. (The $-prompt in this article just indicates that a command is being entered at the command line.)
Prompt
$ % #
Shell
Bourne or Korn C-shell Root login
The simplest way to turn on the execute-bit is using chmod +x capture_vmstat.sh. In a production environment, on an exposed server you must also consider owner, group and world permissions to control complete access to the script. (The topic of file permissions is beyond the scope of this document.) See man chmod for more information.
Figure 5 - This will NOT execute the script unless dot is in the PATH
You must explicitly specify the full file name of the script, including path. Do not rely on the PATH variable because it could get changed in the future and one of two things could go wrong. First, the directory where your scripts reside could be inadvertently removed from the PATH and UNIX would no longer be able to locate your scripts. Worse yet, UNIX might find and execute a script by the same name in a different directory, one listed in the new PATH. Therefore, to be safe, you should always execute your scripts by specifying the full file name as shown in Figure 6.
$ /usr/local/acme/scripts/capture_vmstat.sh
Figure 6 - Specifying the full file name to ensure UNIX finds the correct script
Maybe you dont like typing so a shortcut relies on the fact that . (dot) refers to the current directory. First, change to the script directory and then execute the script by prepending ./ (dotslash) to the script name as shown in Figure 7. This doesnt save much typing if you are only executing one script; however, if you are going to execute several scripts from your script directory, then you only have to type the directory name once.
$ $ cd /usr/local/acme/scripts ./capture_vmstat.sh
Regardless of how you invoke the capture_vmstat.sh script, the output should be identical to what you get when you run vmstat interactively.
1.
Interactively
Document the script and let others (perhaps the Help Desk staff) run the script file. The folks who run the script dont need to know UNIX commands or syntax in much the same way that DOS users dont need to understand DOS commands or syntax in order to use a BAT file created for them.
2.
Use the at command to execute a script once at some time in the future. Check man at for details. Some UNIX systems cancel running at-jobs when a user logs out. Check system documentation carefully.
3.
Use the crontab file to execute a script repeatedly on a fixed schedule. Check man crontab for details. Figure 8 shows a simple crontab entry to run your script once an hour from 8AM-5PM at 10 minutes past the hour every Monday, Wednesday and Friday:
10 8-17 * * 1,3,5 /usr/local/acme/scripts/capture_vmstat.sh
Before moving on to the fourth method for launching your script you need to understand two problems with running scripts via crontab. First, since you are not logged in when the script is executed, you cant rely on Korn shell being the default shell. Therefore, you must be sure to use #!/bin/ksh as the first line of your script as explained in Figure 1. Second, the current version of your script sends its output to the terminal. When cron launches the script there is no terminal so cron must redirect the stdout somewhere. The normal place is to the email inbasket of the user whose crontab launched the script. While this may be acceptable, other (better) solutions are described below when you expand your basic script.
4.
Launch your script using an HTML form and POSTing your script via CGI (common gateway interface). The output of the command will be sent back to the browser so the <PRE> and </PRE> HTML tags should be used to preserve formatting. There is a bit more to this HTML form method than described here, and there are numerous security risks with using FORMs and CGI. However, this method has proven very successful for use by in-house Help Desk staff or other level-one support personnel.
Redirecting stdout
First, the script sends its output to stdout, which is normally the terminal. You can extend the script to redirect the output to a log file as shown in Figure 9.
#!/bin/ksh vmstat 2 30 > /usr/local/acme/logs/vmstat.log
But this introduces a couple of new problems. First, every time you run the script it overwrites the contents of the last log file. To correct that, append the new output to the end of the existing log file. But then you need to know when each output in the log was created, since the date-time stamp on the file only indicates when the last one was written.
In Figure 11, the Korn shell is instructed to run the netstat command, grep for ESTABLISH, and use wc to count the number of lines by enclosing these commands in $(xxx). Further the Korn shell is instructed to store the output of these commands in environment variable CTR_ESTAB. Then in the echo command, the Korn shell is instructed to use the value stored in that environment variable. To use a value that is
stored in an environment variable, put a $ in front of the variable name, e.g. $CTR_ESTAB. To improve readability and to avoid ambiguities, use the Korn shell option of enclosing the variable name inside curly braces, e.g. ${CTR_ESTAB}.
# store current date as YYYYMMDD in variable DATE for later use export DATE=$(date +%Y%m%d) # count number of established socket connections and write to log export CTR_ESTAB=$(netstat na P tcp | grep ESTABLISH | wc l) export CTR_CLOSE_WAIT=$(netstat na P tcp | grep CLOSE_WAIT | wc l) echo ${DATE} ${CTR_ESTAB} ${CTR_CLOSE_WAIT} >> ${LOG_FILE} Figure 11 - Using $(xxx) to execute a command within a Korn shell script
When the next user runs the script, a different PID will be assigned to the scripts execution thus causing a separate log file to be created each time instead of appending to the existing log file. Maybe thats not a bad thing, but its not what you want to achieve. Another possibility, instead of using an environment variable whose value is changed each time the script is executed, is to use an environment variable that is set once, outside the script, prior to the execution of the script. UNIX automatically sets the LOGNAME environment variable whenever a user logs in. In Figure 13, this value is imbedded into the log file name so that each user can have a log file:
#!/bin/ksh echo #--- $(date) >> /usr/local/acme/logs/vmstat.${LOGNAME}.log vmstat 2 30 >> /usr/local/acme/logs/vmstat.${LOGNAME}.log Figure 13 - Generating a file name using an environment variable whose value is externally set
Second, what if you change your mind about the log file naming convention? This is not something you want the user to have to provide each time via a command line argument. However, if you have hard-coded the log file name in multiple lines within the script, then when you decide to use a different naming convention you will have to search every line of the script to see where the name was specified. Instead, store the log file name in an environment variable and modify each command to append output to the file name contained in the variable. Then, when you change the log file naming convention, all you need to do is modify the one line where the environment variable is set.
#!/bin/ksh # ---------------------------------------------------# capture_vmstat.sh <INTERVAL> <COUNT> # <INTERVAL> vmstat interval # <COUNT> vmstat count # run vmstat and capture output to a log file #----------------------------------------------------# indicate defaults for how often and for how long to run vmstat export INTERVAL=2 # every 2 seconds export COUNT=30 # do it 30 times # obtain command line arguments, if present if [ ${1} != ] then INTERVAL=${1} # if there is one command line argument, maybe theres two if [ ${2} != ] then COUNT=${2} fi fi # directories where scripts and logs are stored export PROGDIR=/usr/local/acme/scripts export LOGDIR=/usr/local/acme/logs # define logfile name and location export LOG_FILE=${LOGDIR}/capture_vmstat.${LOGNAME}.log # write current date/time to log file echo #--- $(date) >> ${LOG_FILE} vmstat ${INTERVAL} ${COUNT} >> ${LOG_FILE} # say goodnight, Gracie exit 0 Figure 14 - A more robust version of the capture_vmstat.sh script
Now you need to write a script file that executes this in a loop. The loop should pause for 10 seconds between executions of the ps command. The loop should execute 720 times [every 10 seconds means 6 times per minute or 360 times per hour (60 mins/hr * 6/min) for two hours]. Figure 17 shows a simple while-loop script.
#!/bin/ksh export INTERVAL=10 export COUNT=720 export LOG=/usr/local/acme/logs/while_loop_test.log export CTR=0 while [ true ] do if [ ${CTR} -ge ${COUNT} ] then exit fi echo #------- $(date +%Y%m%d-%H%M%S) >> ${LOG} ps -f -u bv -o pid,pcpu,pmem,rss,vsz,comm >> ${LOG} CTR=$(expr ${CTR} + 1) sleep ${INTERVAL} done Figure 17 - Simple while-loop script
#------- 19991203-123237 PID %CPU %MEM RSS VSZ COMMAND 12007 0.2 0.8 13640 24280 cmsdb 11938 0.0 0.7 11536 20496 sched_poll_d <snip> #------- 19991203-123240 PID %CPU %MEM RSS VSZ COMMAND 12007 0.2 0.8 13640 24280 cmsdb 11938 0.0 0.7 11536 20496 sched_poll_d <snip> #------- 19991203-123243 PID %CPU %MEM RSS VSZ COMMAND 12007 0.3 0.8 13640 24280 cmsdb 11938 0.0 0.7 11536 20496 sched_poll_d <snip> #------- 19991203-123246 <and-so-on>
Figure 18- Output from the while-loop script
2. Always use uppercase when defining variables. Use underscores to separate words.
BIN_DIR=/opt/bv1to1/bin
3. Always export environment variables so that any sub-processes will have automatic access to the values:
export SUPPORT_IDS=userA@domain.com,userB@domain.com
4. To execute a UNIX command and use the output elsewhere in a Korn shell script, type a $, enclose the command within parentheses, and store the output in an environment variable.
export CTR_ESTAB=$(netstat na | grep ESTABLISH | wc l)
5. To use a value that is stored in an environment variable, put a $ in front of the variable name. To improve readability and to avoid ambiguities, enclose the variable name inside curly braces.
echo The number of ESTABLISHED connections is ${CTR_ESTAB}
6. To ensure having a unique file name, use $$ to include the PID number in the file name. Insert the PID number into the file name just prior to the file extension:
export LOG_FILE=/tmp/capture_vmstat.$$.log
8. Precede a script name with dot-slash when executing interactively so UNIX knows that the script is in the current directory.
./capture_vmstat.sh
9. Redirect stdout ( > ) to a log file or append stdout ( >> ) to a log file.
./capture_vmstat.sh >> ${LOG_FILE}
10. Redirect stderr, either to the same destination as stdout or to a unique file.
./capture_vmstat.sh >> ${LOG_FILE} 2>&1 - or ./capture_vmstat.sh >> ${LOG_FILE} 2>>${ERR_LOG}
References
Unix Shell Programming (Hayden Books Unix System Library) by Stephen G. Kochan, Patrick H. Wood Paperback - 490 pages 2nd Revised edition (January 1990) Hayden Books; ISBN: 067248448X
Author Biography
Ken Gottry is a Sr. Infrastructure Architect with NerveWire, Inc. He has 30 years experience with systems ranging from mainframes, to minicomputers, to servers, to desktops. For the past 10 years his focus has been on designing, implementing and tuning distributed, multi-tier, and web-based systems. As a performance engineer consulting to numerous G2K firms, he has assessed and tuned web servers, app servers and JVM's running on Solaris. Kens articles have appeared on Suns dot-com builder website (http://dcb.sun.com) and iPlanets developer website (http://developer.iplanet.com). Also. Ken has recently published an article about Solaris performance tuning in SysAdmin magazine (http://www.sysadminmag.com). Ken Gottry 506 Babcock Road Tully, NY 13159 mobile: 315.383.5249 fax: 315.696.0065 ken@gottry.com kgottry@nervewire.com