Korn Shell Programming
A FIRST PROGRAM AND A FIRST CONDITIONAL
Here's what you might consider to be your first script - it's four lines of code calling up mkdir, cp, ls, wc and sed programs and using a shell builtin echo to perform a backup, into a newly created directory named after the current date.
#!/usr/bin/ksh
# Korn Shell script (1) to back up files
mkdir `date +%F`
cp *.dat `date +%F`
echo -n "Backup copy(ies) made - file count = "
ls `date +%F` | wc -l | sed "s/[ \n]*//g"
How does it work?
$ ./dat_backup
Backup copy(ies) made - file count = 1
$ ./dat_backup
mkdir: cannot create directory `2007-09-22': File exists
Backup copy(ies) made - file count = 1
$
Very well the first time, but then it failed. And that's because mkdir tried to create a directory that already existed!
We can add to that code using a conditional - this first example uses a [ notation which the Korn shell inherits from the Bourne shell - a little ugly, but it works well enough:
#!/usr/bin/ksh
# Korn Shell script (2) to back up files
# Uses bourne shell compatible tests
if [ ! -e `date +%F` ]
then
mkdir `date +%F`
echo "Making new directory"
else
echo "Overwriting directory"
fi
cp *.dat `date +%F`
echo -n "Backup copy(ies) made - file count = "
ls `date +%F` | wc -l | sed "s/[ \n]*//g"
And running:
$ ./dat_backup2
Overwriting directory
Backup copy(ies) made - file count = 1
$
Finally, a more flexible and potentially less ugly structure using [[ for the brackets is provided in the Korn shell. This is the one we'll carry on using later in this material.
$ r 145
rm -rf 2007-09-22
$ ./dat_backup3
Making new directory
Backup copy(ies) made - file count = 1
$ ./dat_backup3
Overwriting directory
Backup copy(ies) made - file count = 1
$
Here's the code I used:
#!/usr/bin/ksh
# Korn Shell script (3) to back up files
# Uses bourne shell compatible tests
if [[ ! -e `date +%F` ]]
then
mkdir `date +%F`
echo "Making new directory"
else
echo "Overwriting directory"
fi
cp *.dat `date +%F`
echo -n "Backup copy(ies) made - file count = "
ls `date +%F` | wc -l | sed "s/[ \n]*//g"
ARRAYS AND LOOPS
In the Korn shell, all variables are actually arrays - yo do not declate them as such but simple set and get elements using a [ to ] notation. When referencing an element with the shell for reading, you have to help the shell with an extra { to } to delimit the variable name.
#!/usr/bin/ksh
# Setting up and referencing a Ksh array
county=Wiltshire
town[0]=Swindon
town[1]=Salisbury
town[2]=Chippenham
town[3]=Trowbridge
town[4]=Melksham
town[5]='Wootton Bassett'
print "We know about" ${#town[*]} towns in $county
print There is ${town[2]}
print There is ${town[4]}
print There is ${town[5]}
Note also the ${#town[*]} notation to count all members with any key.
$ ./pippa
We know about 6 towns in Wiltshire
There is Chippenham
There is Melksham
There is Wootton Bassett
$
The Korn shell supports while, until and for loops. Let's use examples to loop through our array of towns:
$ ./tracey
We know about 6 towns in Wilthshire
There is Swindon
There is Salisbury
There is Chippenham
There is Trowbridge
There is Melksham
There is Wootton Bassett
$
Here's the program for that:
#!/usr/bin/ksh
county=Wilthshire
town[0]=Swindon
town[1]=Salisbury
town[2]=Chippenham
town[3]=Trowbridge
town[4]=Melksham
town[5]='Wootton Bassett'
print "We know about" ${#town[*]} towns in $county
let a=0
while [[ $a -lt ${#town[*]} ]] ; do
print There is ${town[$a]}
let a+=1
done
It's down to which style you choose to use as to whether or not you put the do on the same line as the while, but if you elect to do so you must use that extra ; character.
The let command forces a piece of simple arithmentic (Korn shell maths is integer).
In an instance like this one, where the actual number associated with the town doesn't really matter to us, we may prefer to use a for loop to go through each of the values that's contained in the array, or a part of the array. Note that this for loop structure is quite different to the for loop structure in the C language if you're familiar with that.
$ ./sharon
We know about 6 towns in Wiltshire
There is Swindon
There is Salisbury
There is Chippenham
There is Trowbridge
There is Melksham
There is Wootton
There is Bassett
$
Notice the fact that my script turned "Wootton" and "Bassett" into two different towns!
#!/usr/bin/ksh
# for loop - Korn Shell
county=Wiltshire
town[0]=Swindon
town[1]=Salisbury
town[2]=Chippenham
town[3]=Trowbridge
town[4]=Melksham
town[5]='Wootton Bassett'
print "We know about" ${#town[*]} towns in $county
for place in ${town[*]}; do
print There is $place
done
OTHER BRANCH CONTROLS
You have already seen if and else. There is also a case available, and you can test implicit conditions within valiables and calculations.
Also available are multiway branch case.
$ ./townline
Where are you wondering about
Orpington
Origin - House, Farm or group of huts
$ ./townline
Where are you wondering about
Chippenham
Saxon Origin
Origin - Village
$
Here's the source:
#!/usr/bin/ksh
# Selects a town ending
print "Where are you wondering about "
read placename
case $placename in
*ham )
print "Saxon Origin"
result="Village";;
*ton )
result="House, Farm or group of huts";;
*bridge )
result="River Crossing";;
*bury )
result="Farm or Village";;
*minster )
print "Saxon Origin"
result="Church";;
* )
result"Not in my table!";;
esac
print "Origin - $result"
OTHER LOOPS
You have already seen while and for.
There is also an until loop and a select statement which prompts for user entry and loops on error
$ ./selk
Where to you want to travel?
1) Swindon
2) Chippenham
3) Melksham
4) Trowbridge
5) Westbury
town? 7
invalid. Please try again
town? 3
You have entered Melksham
$
And the script that uses that select:
#!/usr/bin/ksh
PS3='town? '
print "Where to you want to travel?"
select place in Swindon Chippenham Melksham Trowbridge Westbury; do
if [[ -n $place ]]; then
print You have entered $place
break
else
print 'invalid. Please try again'
fi
done
AT THE END OF YOUR CONDITIONAL
You have already see fi and done and esac. Let's look at break, continue and retun.
break says "get me out of this loop .... NOW"
continue says "go back to the top of this loop for the next iteration"
and return says "leave this function .... NOW".
return takes a value as a parameter - the value to be returned
LOGICAL TESTS AND BALANCES
Exit statuses are the only things an if construct can test. But that doesn't mean you can check only whether or not commands ran properly. The shell provides a way of testing a variety of conditions with the [[ ]] construct.
Example of status check:
if cd ${dirname:?"missing directory name."}
Example of using the [ command (yes, really) to test a condition:
if [
And using the [[ construct.
if [[
STRING COMPARISONS
Use the operators = != < > -n and -z (careful - it's a single = sign!); -n for NOT null and -z for zero length.
Code sample:
#!/usr/bin/ksh
# String Test Operators - Ksh
print "Enter your name "
read something
if [[ $something = "Graham" ]] ; then
echo "Hello Graham"; fi
if [[ $something != "Graham" ]] ; then
echo "Hello Someone Else"; fi
if [[ $something < "Graham" ]] ; then
echo "Hello Less Than Graham"; fi
if [[ $something > "Graham" ]] ; then
echo "Hello Greater Than Graham"; fi
if [[ -n $something ]] ; then
echo "Hello Someone"; fi
if [[ -z $something ]] ; then
echo "Hello nobody"; fi
Testing ...
$ ./stroppy
Enter your name
Graham
Hello Graham
Hello Someone
$ ./stroppy
Enter your name
Harry
Hello Someone Else
Hello Greater Than Graham
Hello Someone
$ ./stroppy
Enter your name
Hello Someone Else
Hello Less Than Graham
Hello nobody
$
NUMERIC COMPARISONS
Remember - the Korn shell is an integer shell. Have a look at the bc command if you need to do floating point work.
Numeric comparisons are -eq, -ne, -lt, -le, -ge and -gt. Example:
#!/usr/bin/ksh
# Korn Shell - numeric (integer) testing
print -n "Please enter your age "
read yourage
if [[ $yourage -eq 21 ]] ; then
print "Congratulations"; fi
if [[ $yourage -ne 21 ]] ; then
print "Just another year then"; fi
if [[ $yourage -lt 21 ]] ; then
print "A young one!"; fi
if [[ $yourage -ge 21 ]] ; then
print "You're 21 on over"; fi
if [[ $yourage -le 21 ]] ; then
print "You say you're 21 or less"; fi
if [[ $yourage -gt 21 ]] ; then
print "A mature one"; fi
Testing code:
$ ./numtee
Please enter your age 33
Just another year then
You're 21 on over
A mature one
$ ./numtee
Please enter your age 13
Just another year then
A young one!
You say you're 21 or less
$ ./numtee
Please enter your age 21
Congratulations
You're 21 on over
You say you're 21 or less
$
FILE TESTS
-a file - file exists
-d file - file is a directory
-f file - file is a regular file
-r file - You have read permission on file
-s file - file exists and is not empty
-w file - You have write permission on file
-x file - You have execute permission on file
-O file - You own file
-G file - Your group ID is the same as that of file
file1 -nt file2 - file1 is newer than file2 (based on modification date)
file1 -ot file2 - file1 is older than file2
Let's try those out .. and also start using some command line arguments, which are available in ksh.
$* - all the command line parameters (see $@ if they may be null or have space) $1, $2, $3 etc - command line parameters one by one
#!/usr/bin/ksh
# Comparing one or two files
if [[ -z $1 ]] ; then
print -u2 "Usage $0 filename [secondfilename]"
exit
fi
# If two files are given find which is newer
if [[ -n $2 ]] ; then
if [[ $1 -nt $2 ]] ; then
print "$1 is newer"
use=$1
else
print "$2 is newer"
use=$2
fi
else
use=$1
fi
print "About file $use ..."
if [[ ! -a $use ]]; then
print "file $1 does not exist."
return 1
fi
if [[ -d $use ]]; then
print -n "$use is a directory that you may "
if [[ ! -x $use ]]; then
print -n "not "
fi
print "search."
elif [[ -f $use ]]; then
print "$use is a regular file."
else
print "$use is a special type of file."
fi
And some tests of that:
$ ./filestat sharon tracey
tracey is newer
About file tracey ...
tracey is a regular file.
$ ./filestat tracey sharon
tracey is newer
About file tracey ...
tracey is a regular file.
$ ./filestat .. .
. is newer
About file . ...
. is a directory that you may search.
$ ./filestat pippa
About file pippa ...
pippa is a regular file.
$ ./filestat pulla
About file pulla ...
file pulla does not exist.
$
See also
Linux Basics Course