KornShell (ksh)

Reading Input, Conditionals & Cutting Fields

5 Writing a Script File

So far all the examples have been single commands typed at the prompt. Once you have more than one or two lines, or want to run the same sequence of commands again, you need a script file. A script is simply a plain text file containing shell commands, one per line. The shell reads them in order executes each one in turn unless it encounters instructions to change the order, exactly as if you had typed them yourself.

5.1 Creating a Script with vi

Use vi to create the file. Give it a meaningful name — there is no need for a file extension. To create a script called greet, run:

vi greet

vi opens in command mode. Press i to enter insert mode, then type your commands. When you are finished, press Esc to return to command mode, then type :x and press Enter to write the file and quit.

5.2 Making the Script Executable

A newly created file has no execute permission. Use chmod +x to add it:

chmod +x greet

You only need to do this once. After that, you can run the script by typing its name preceded by ./ to tell the shell to look in the current directory:

./greet
Note: The ./ prefix is necessary because the current directory is not normally in the shell’s search path. Without it, the shell looks for greet in the standard system directories and will not find your script.

5.3 A First Script

Here is a simple script that asks for a name, works out the day of the week, and prints a greeting. It uses only commands covered in earlier sections.

Note that if the shell encounters a # character it ignores the remainder of the line. This allows you to enter comments. If you start a line with a # the whole line is ignored. And blank lines do nothing. Using comments and blank lines will make your script easier to understand.

Create the file with vi greet, type the following, then save and quit:

name=$LOGNAME
dow=$(date +%A)
echo "Hello $username, happy $dow"
Hello root, happy Tuesday

LOGNAME is a system variable that holds the login name of the user. We’re copying it to a local variable here, just so it looks neater.

The date command accepts a format string beginning with +. The %A specifier expands to the full name of the current day of the week. The result is captured into the variable dow using command substitution, then passed to echo along with the name the user entered.

Tip: To see what other format specifiers date understands, run: man date

7 Reading User Input

The read command reads one line from standard input and stores it in a named variable. On its own, read gives no prompt. It simply waits silently until you type something and press enter, so we’re using the printf command to output a prompt.

7.1 Prompting with printf

printf "prompt text: "
read varname

Unlinke echo, which always prints a newline after it’s output, printf keeps the cursor on the same line. This looks much neater for the user. You might see some shells that support echo -n to suppress the newline, but this is not consistent across all Unix systems. printf is part of the POSIX standard and behaves identically everywhere, making it the more portable and reliable choice. Most modern shells also support a -p parameter for read, which allows you to specify a prompt. Ksh on AIX doesn’t.

printf "Enter your name: "
read username
echo "Hello, $username"
Enter your name: Alice
Hello, Alice

Once stored, the variable behaves identically to one you assigned directly. You can pass it to commands, embed it in strings, use it in comparisons, or store further processing results into it. Here’s a more complex example:

printf "Enter a filename: "
read fname
linecount=$(wc -l < $fname)
echo "$fname has $linecount lines"
Enter a filename: /etc/passwd
/etc/passwd has 47 lines

Here the value the user types is passed to wc, and the result is stored in a second variable. The < redirection feeds the file to wc, which then counts the lines and prints the total. This is then assigned to linecount.

7.2 Reading Multiple Variables

You can name more than one variable on a single read command. The shell splits the input on whitespace and assigns each word to the corresponding variable. Any leftover words all go into the last variable named.

printf "First name, last name: "
read first last echo "$last, $first"
First name, last name: Alice Smith
Smith, Alice

8 Conditional Execution: if…then…else…fi

The if statement in the shell works differently from most programming languages. It does not evaluate a boolean expression as in most languages, but it runs a command and responds to whether that command succeeded or failed.

Every command in the shell exits with a numeric return code when it finishes. Generally, 0 means success, and any non-zero value means failure; but you should always check the man page. The if statement treats 0 as true and non-zero as false. This is the same convention used everywhere in Unix, so any command can be used directly as the condition of an if.

8.1 Structure

if command
then
# runs if command exited 0 (success / true)
else

# runs if command exited non-zero (failure / false)
fi
Note: The else branch is optional. A plain if…then…fi is valid when you only need to act on success. Also note that you can have multiple lines between then…else and fi.

The if statement must be terminated with a fi, which is if reversed. Reversing the statement that started the block is a common way to end a block in Unix scripts.

8.2 The test Command

Before combining test with if, it is worth understanding what test does on its own. test is a regular command that evaluates a condition and sets its exit code accordingly: it exits 0 if the condition is true, and 1 if the condition is false. When a command completes it sets the exit value to a special variable called $?. You can see what directly by running test and then examining $?

One thing test can do is compare two numbers. The form is to give it the first number, then a comparison operator, then the second number. It’d be great if you could write “test 4 > 3” but unfortunately it would interpret the > as the output redirection character, so instead options like -gt, -lt, -eq, and -ne are used for Greater Than, Less Than, EQual or Not Equal.

test 5 -gt 3
echo $?
0
test 2 -gt 10
echo $?
1

The exit code 0 means true (the condition held) and 1 means false (it did not). This is the opposite of most programming languages, but it is consistent with how every Unix command signals success or failure.

test can also compare strings and test whether files exist using other options. For example, -f followed by a filename will return true if the file exists.

test -f /etc/passwd
echo $?
0 (file exists — true)
test -f /etc/nosuchfile
echo $?
1 (file does not exist — false)

The test command supports very many things you might want to check, and you should see the man page for a full list. Here are some common ones.

OperatorMeaning
n1 -eq n2equal (numeric)
n1 -ne n2not equal (numeric)
n1 -lt n2less than
n1 -gt n2greater than
n1 -le n2less than or equal
n1 -ge n2greater than or equal
s1 = s2equal (string)
s1 != s2not equal (string)
-z s1string is empty
-n s1string is not empty
-f filenamepath exists and is a regular file
-d filenamepath exists and is a directory

8.3 Using test with if

Because if runs a command and checks whether it exits 0, and because test exits 0 when its condition is true, the two fit together naturally. You simply put the test command where if expects a command:

printf “Enter your age: “
read age
if test $age -ge 18
then
echo “Access granted.”
else
echo “Access denied.”
fi
Enter your age: 21
Access granted.

A practical example using a file check before processing:

printf "File to process: "
read datafile

if test -f $datafile
then
echo "Found $datafile - processing..."

wc -l $datafile
else

echo "File not found: $datafile"
fi
File to process: /etc/passwd
Found /etc/passwd - processing...
47 /etc/passwd

8.4 elif for Multiple Branches

Use elif to add further conditions before the final else:

printf "Enter a number: "
read num
if test $num -gt 0
then
echo "Positive"
elif test $num -lt 0
then
echo "Negative"
else
echo "Zero"
fi
Enter a number: -4 Negative

8.5 Using Any Command as the Condition

Because if simply runs a command and checks its exit code, any command works as the condition — not just test. A very common pattern is to use grep directly:

printf "Username to look up: "
read searchuser
if grep $searchuser /etc/passwd > /dev/null 2>&1
then
echo "User exists."
else
echo "No such user."
fi
Username to look up: alice
User exists.

grep exits 0 when it finds a match and non-zero when it does not. The if responds to that exit code. The output is redirected to /dev/null so grep doesn’t print anything on the screen. Only the exit code matters here.

Tip: Redirect both standard output and standard error with > /dev/null 2>&1 when running a command purely for its exit code and you do not want any output to appear.

9 The [ Alias for test

Some people prefer their if statements to have an expression in brackets, as they might be familiar with from other languages. You can do this in ksh because the test command has an alias: [. You can find it at /usr/bin/[ . And yes, [ is a valid Unix filename!

It works exactly the same as test, except it expects a closing ] as its final argument. The ] is not special syntax — it is simply a required parameter that [ checks for and then ignores. The following lines are equivalent:

if test $age -ge 18
if [ $age -ge 18 ]

Notice that there must be a space before the ] — it is a separate argument to [, not part of the syntax. Forgetting the space is a common mistake that produces a confusing error message.

Here is a complete example using [ ] in place of test:

printf “Enter your age: “
read age
if [ $age -ge 18 ]
then
echo "Access granted."
else
echo "Access denied."
fi
Enter your age: 21
Access granted.
Note: The choice of test or [ is purely a matter of style and readability. They are exactly the same command. You will see both in scripts you encounter, so it is worth being comfortable with either form.

10 Cutting Fields from Command Output

Many commands produce output in columns: ps, df, who, and ls -l are common examples. The cut command extracts specific fields from each line, either by character position or by a named delimiter character. The -d flag sets the delimiter and -f selects which field or fields to extract, numbered from 1.

10.1 Clean Delimiters: /etc/passwd

The cleanest approach is to work with output that already uses a consistent single character as a separator. The /etc/passwd file is a good example — fields are always separated by a single colon, with no padding:

# Extract username (field 1) and home directory (field 6)
cut -d: -f1,6 /etc/passwd
root:/
daemon:/etc
...
alice:/home/alice

This is extracting fields one and six from the password file – i.e. the username and their home directory. Note the output is also delimited with the delimiter character, in this case :, if you are extracting multiple fields.

Combining cut with grep and command substitution lets you pull a specific value into a variable:

homedir=$(grep "^${LOGNAME}:" /etc/passwd | cut -d: -f6)
echo "Your home directory is: $homedir"
Your home directory is: /home/alice

The grep anchors on the start of a line (^) followed by your login name and a colon, so it matches exactly one line. The cut then extracts field 6 from that line. No space normalisation is needed because the delimiter is always a single colon.

Tip: Prefer commands whose output uses a fixed single-character delimiter. Files like /etc/passwd, /etc/group, and most configuration files in /etc use colons or commas, and are the most reliable source material for field cutting.

10.2 Space-Separated Output and tr

Commands like ps, df, and who separate fields with spaces, but typically use multiple spaces for visual alignment. This is a problem on AIX because cut does not support the -w flag found on BSD systems, which treats any run of whitespace as a single delimiter. When you pass -d’ ‘ to cut on such output, every individual space counts as its own delimiter, making field numbers unpredictable.

The solution is to normalise the whitespace first using the tr command, which “transforms” data. In this case we’re going to use tr with the -s, which squeezes any run of repeated characters down to a single one:

tr -s ‘ ‘

For example:

ls | wc
           38    38    270

This is no good as you don’t know how many spaces there are before the digits.

ls | wc | tr -s ' '
38 38 270

Here is a practical example extracting available disk space in /var from df output. You can give df a mount-point as a parameter if you don’t want everything listed.

df /home
Filesystem   512-blocks      Free     %Used  Iused  %Iused   Mounted on 
/dev/hd1 65536 60512 8% 55 1% /home

As you can see, we have a header line we don’t want, and the free space is field three. And there are a random number of space between fields. We’ll need to clean this up to get our number.

# df output has variable spacing between columns
# tail -1 skips the header line
# tr -s ' ' squeezes multiple spaces to one
# cut -d' ' -f3 extracts the third field

avail=$(df /home | tail -1 | tr -s ' ' | cut -d' ' -f3)
echo "Available blocks on /home: $avail"
Available blocks on /home: 2457600

Putting it together in a script that warns when disk space is low:

avail=$(df /home | tail -1 | tr -s ' ' | cut -d' ' -f3)
if test $avail -lt 100000
then
echo "WARNING: Low disk space on /home ($avail blocks remaining)"
fi
Note: Some df output on some versions of AIX has a leading space on data lines, which creates an empty first field after splitting on spaces. If your field numbers seem off by one, adjust the field number accordingly.

10.3 Extracting Specific Fields from ps

Another common use is extracting the process ID from ps output. The -ef flags give a full listing in a consistent format:

printf “Process name to find: ”
read procname
pid=$(ps -ef | grep “$procname” | grep -v grep | tr -s ‘ ‘ | cut -d’ ‘ -f2)
if test -n “$pid”
then
echo “$procname is running as PID $pid”
else
echo "$procname is not running"
fi
Process name to find: cron
cron is running as PID 1234
Tip: The grep -v grep step removes the grep command itself from the results — without it, grep would find its own process in the ps listing and include it in the output.

11 Exercises

Work through the following exercises at a ksh prompt. Type the commands as shown, observe the output, then experiment with variations.

Section F — Reading Input

Exercise 16: Write a short script that asks the user for a directory name and then reports how many files are in it. Use printf for the prompt, read to collect the input, ls to list the directory, and wc -l to count. Hint: dircount=$(ls $dirname | wc -l)
Exercise 17: Ask the user to enter two words on one line (e.g. a first name and a last name). Use printf for the prompt, then read to capture both words into two separate variables. Echo them back in reverse order. Hint: printf “First and last name: “; read first last
Exercise 18: Ask the user for a filename. Use printf for the prompt and read to collect it. Check whether the file exists using test -f. If it does, report the number of lines it contains using wc -l. If it does not exist, print a suitable error message. Hint: if test -f $fname

Section G — Conditionals

Exercise 19: At the command prompt (not in a script), run a test command to check whether 42 is greater than 100. Then check $? to see the exit code. Repeat with 100 -gt 42 and confirm the exit code changes. Hint: test 42 -gt 100; echo $?
Exercise 20: Write a script that asks the user to enter a number. Use test and if…elif…else to print whether the number is positive, negative, or zero. Hint: test $num -gt 0
Exercise 21: Ask the user for a username. Use grep against /etc/passwd (suppressing output with > /dev/null 2>&1) as the condition of an if. Print a message saying whether the user exists. Hint: if grep “^$user:” /etc/passwd > /dev/null 2>&1
Exercise 22: Write a script that asks the user for a path. Use test -d to check whether it is a directory, test -f to check if it might be a plain file, and print an appropriate message for each case. Hint: if test -d $path … elif test -f $path

Section H — Cutting Fields

Exercise 23: Use cut with the colon delimiter to extract just the login shell (field 7) for every user in /etc/passwd. Pipe the result through sort -u to list the unique shells in use. Hint: cut -d: -f7 /etc/passwd | sort -u
Exercise 24: Use grep to find your own entry in /etc/passwd (use $LOGNAME), then cut out your username and your home directory and print them on one line. Hint: grep “^${LOGNAME}:” /etc/passwd | cut -d: -f1,6
Exercise 25: Run df on the root filesystem (df /). Use tail, tr -s ‘ ‘, and cut to extract the percentage used figure. Store it in a variable and print it. Hint: df / | tail -1 | tr -s ‘ ‘ | cut -d’ ‘ -f5
Exercise 26: Use ps -ef to list all processes. Pipe through grep to find processes owned by root, then use tr -s ‘ ‘ and cut to extract just the process IDs (field 2). Pipe the result through head -5 to show the first five. Hint: ps -ef | grep ‘^root’ | tr -s ‘ ‘ | cut -d’ ‘ -f2 | head -5

End of exercises — good luck!

Leave a Reply

Your email address will not be published. Required fields are marked *