Introduction

This lecture looks at more shell programming. It looks at more advanced features which may be needed on occassions.

Debugging

A shell script can be debugged by turning tracing on. When a script is being traced, before each command is executed, the command complete with arguments is printed. for i in 1 2 3 do echo $i done is traced as + echo 1 1 + echo 2 2 + echo 3 3 Tracing is turned on by set -x and off by set +x or a shell script, say ``script1'' may be run in debug mode by sh -x script1 set -x for i in one two "three four" do echo 'one two three four' | sed "s/$i//" done

More on sequencing

The sequential mechanism is to have commands on successive lines, or separated by `;'. There are additional ways. To run a command asynchronously command & The command then runs in parallel with whatever else is done, timesharing the computer. # set the PATH to include X programs PATH=$PATH:/usr/X11/bin xclock & xcalc &

The sequence

command1 && command1 first executes command1. If its exit code is zero (it succeeds) then command 2 is executed. If the exit code is non-zero, then command2 is not executed and execution continues to the next command. It is equivalent to if command1 then command2 fi It is typically used as a shorthand for things like test -f file && rm file which attempts to remove the file only if it exists.

The sequence

command1 || command2 executes command1, and if it fails executes command2 test -f file || \ (echo can\'t find file && exit 1) Enclosing a command in (...) executes the command in a subshell. This is useful if you want to make changes to the environment for some commands without affecting others. For example, to store the parent directory in a variable ParentDir=`(cd ..; pwd)` This is easier than using a sed command which looks for a `/' and then characters that aren't a `/': ParentDir=`pwd | sed 's/\/[^/]*//'`

Builtin shell variables

There are a large number of variables builtin to the shell. These are global variables that can be used anywhere. To see the full list of variables set in your environment, run set set | more

Builtin shell commands

The shell recognises these commands internally.

alias

alias name=value When an alias is set up, typing the alias name as the first word on a line expands to the value before execution. alias ll="ls -l" This works for shells such as bash, not for the Bourne shell.

break

Break from any enclosing for, case or while loop. This is used to terminate execution of a loop at a point within a loop

continue

Continue the for loop back at the top. for i in * do if [ ! -r $file ] then echo cant read $file continue fi # process readable file done

export

Variables that you create are by default local to that shell. To make a variable global so that it is visible in subshells it must be exported amt=20 export amt

read

Read a line of input. Assign the first word to the first variable, the second to the second and so on. If there are more words on the input line than there are variables, assign the string of the rest of the words to the last variable. read word1 word2 rest

shift

Shifts all positional args to the left i.e. loses the old $1, renames $2 as $1, $3 as $2, etc. As a result, $* now contains as $1, $2, ... what used to be $2, $3, ... echo $1 shift echo 'args $2 upwards are' $*

trap

Trap a signal. Signals are sent to a process usually to tell it to give up. Some signals are SIGHUP (the serial line has hung up), SIGINT (control-C), SIGFPE (floating point exception). Signals are known by their numbers, and SIGINT is 2. If a signal is trapped, execute given code when the signal occurs. trap 'echo "cleaning up" rm tmp* exit 2' 2

true

This command always succeeds.

Functions

Functions may be defined within some shells. bash uses the keyword function. These are faster than calling shell scripts, but less portable. function ll { ls -l $* } The positional parameters $1, $2, ..., $*, $# refer now to the arguments with which the function is called.

The test command

Test is a command used for testing one or more conditions in an if command. Format

test expression or [ expression ] If expression is true, test returns an exit status of zero. If expression is not true (i.e. false), test returns an exit status of non-zero. The $? shell variable is automatically set to the exit status of the last command executed.

Test String Operators

Command Returns TRUE (exit status 0) if

Test Integer Operators

Command Returns TRUE (exit status 0) if

Test File Operators

Command Returns TRUE (exit status 0) if

Logical Operators for TEST

Logical AND operator -a

-a performs a logical AND of two expressions. [ -f "$myfile" -a -r "$myfile" ] returns a zero exit status (i.e. true) if $myfile is an ordinary file and is readable by you. -a has a lower precedence than integer and string comparison operators and file operators. Use parentheses to alter precedence order.

Logical OR operator -o

This operator performs a logical OR between two expressions. The -o operator has a lower precedence than the -a operator.

Logical negation operator !

! in front of any test expression to negate the result of the evaluation. [ ! -r /usr/edp/filea ] returns a zero exit status (i.e. true) if /usr/edp/filea is not readable.

Miscellaneous examples

A simple interpreter

my_prompt="mysh>" while true do echo $my_prompt read line sh $line done

Restricted shell

A restricted shell that only accepts a small number of commands: while true do echo "Commands are: edit file list quit" read comm arg case $comm in edit) vi $arg;; list) ls -l;; quit) exit 0;; esac done

Sum file sizes

Sum the size in bytes of all non-directory files in the current directory <xmp> function fifth_arg { echo $5 } sum=0 for file in * do if [ ! -d $file ] then file_ls=`ls -l $file` size=`fifth_arg $file_ls` let sum=$sum+$size fi done echo $sum

Automated testing

Automated testing of programs may be done easily. Suppose that you have a set of files called testdata1, testdata2, ... that are the input files for a program called ``program''. The correct output results are in files result1, result2, ... To test a program, you need to run the program with input the test files and compare them to the result files. The file testall contains: i=1 while [ -f testdata$i ] do test1 $i let i=$i+1 done The file test1 contains program < testdata$1 > results if [ ! -f results ] then echo "Test $1 had no result" exit 1 fi if cmp results result$1 then echo "Result of test $1 ok" rm -f results exit 0 else echo "Test $1 failed" echo "Differences are:" diff results result$1 rm -f results exit 2 fi

Some (more) shell scripts

Exit codes

Your program should exit with 0 if successful, some other number otherwise. The statement exit without a code leaves the code undefined. If you use an exit code in one place, you must give one for all exit statements.

Checking arg counts

if [ $# -ne 3 ]
then
    echo "Usage: $0 arg1 arg2 arg3
    exit 1
fi
or
test $# - ne 3 && echo Usage: $0 a1 a2 a3 && exit 1

Checking arg type

if [ ! -r $1 ]
then
    echo $1 is unreadable
    exit 2
fi

Strings and pipes

echo "..." | ...
cat file | ...
The first sends an explicit string down a pipeline, the second sends the contents of a file down a pipeline.

sed

sed is often used to delete text from lines, or to keep some text in lines.

To delete text, you match the text and replace it with nothing.

sed 's/text//'

To keep text, you match it and save it as \1, \2, etc. The rest of the text is matched as well. The replacement text is only the saved stuff.

sed 's/stuff\(text\)stuff/\1/'

Loops

Loops can be for or while loops.

Write a shell script that lists all the executable files in the current directory

for file in `ls`  # or for file in *
do
    if [ -x $file ]
    then
	echo $file
    fi
done
or
ls |
while read file
do
    if [ -x $file ]
    then
	echo $file
    fi
done

This page is copyright Jan Newmarch.
It is maintained by Jan Newmarch.
email: jan@newmarch.name
Web: http://jan.newmarch.name/
Last modified: 13 Mar, 2001