UNIX/Linux Shell CheatSheet : free A4
PDF (52Kb) |
PNG (90Kb)
. ./library.shgoes at the start of the script.
There could be some confusion about whether to call shell functions procedures or functions; the definition of a function is traditionally that is returns a single value, and does not output anything. A procedure, on the other hand, does not return a value, but may produce output. A shell function may do neither, either or both. It is generally accepted that in shell scripts they are called functions.
A function may return a value in three ways:
exit command to end the shell scriptreturn command to end the function, and return the supplied value to the calling section
of the shell script
This is rather like C, in that exit stops the program, and return returns
control to the caller. The difference is that a shell function cannot change its parameters, though
it can change global parameters.
A simple script using a function would look like this:
#!/bin/sh
# A simple script with a function...
add_a_user()
{
USER=$1
PASSWORD=$2
shift; shift;
# Having shifted twice, the rest is now comments ...
COMMENTS=$@
echo "Adding user $USER ..."
echo useradd -c "$COMMENTS" $USER
echo passwd $USER $PASSWORD
echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}
###
# Main body of script starts here
###
echo "Start of script..."
add_a_user bob letmein Bob Holness the presenter
add_a_user fred badpassword Fred Durst the singer
add_a_user bilko worsepassword Sgt. Bilko the role model
echo "End of script..."
Line 4 identifies itself as a function declaration by ending in ().
This is followed by {, and everything following to the
matching } is taken to be the code of that function.
This code is not executed until the function is called. Functions are
read in, but basically ignored until they are actually called.
Note that for this example the useradd and passwd
commands have been prefixed with echo - this is a useful
debugging technique to check that the right commands would be executed.
It also means that you can run the script without being root or adding
dodgy user accounts to your system!
We have been used to the idea that a shell
script is executed sequentially. This is not so with functions.
In this case, the function add_a_user is read in and checked for syntax,
but not executed until it is explicitly called.
Execution starts with the echo statement "Start of script...". The next line, add_a_user bob letmein Bob Holness is
recognised as a function call so the add_a_user function is entered
and starts executing with certain additions to the environment:
$1=bob $2=letmein $3=Bob $4=Holness $5=the $6=presenter
So within that function, $1 is set to bob,
regardless of what $1 may be set to outside of the function.
So if we want to refer to the "original" $1 inside the function,
we have to assign a name to it - such as: A=$1 before we call the function.
Then, within the function, we can refer to $A.
We use the shift command again to get the $3 and
onwards parameters into $@.
The function then adds the user and sets their password. It echoes
a comment to that effect, and returns control to the next line of the main
code.
Programmers used to other languages may be surprised at the scope rules
for shell functions. Basically, there is no scoping, other than the
parameters ($1, $2, $@, etc).
Taking the following simple code segment:
#!/bin/sh
myfunc()
{
echo "I was called as : $@"
x=2
}
### Main script starts here
echo "Script was called with $@"
x=1
echo "x is $x"
myfunc 1 2 3
echo "x is $x"
scope.sh a b c, gives the following output:
Script was called with a b c x is 1 I was called as : 1 2 3 x is 2
The $@ parameters are changed within the function to reflect
how the function was called. The variable x, however, is
effectively a global variable - myfunc changed it, and that
change is still effective when control returns to the main script.
A function will be called in a sub-shell if its output is piped somewhere
else - that is, "myfunc 1 2 3 | tee out.log" will still say "x is 1" the second time around. This is because a new shell process is called to pipe myfunc(). This can make debugging very frustrating; Astrid had a script which suddenly failed when the "| tee" was added, and it is not immediately obvious why this must be. The tee has to be started up before the function to the left of the pipe; with the simple example of "ls | grep foo", then grep has to be started first, with its stdin then tied to the stdout of ls once ls starts. In the shell script, the shell has already been started before we even knew we were going to pipe through tee, so the operating system has to start tee, then start a new shell to call myfunc(). This is frustrating, but well worth being aware of.
Functions cannot change the values they have been called with, either - this
must be done by changing the variables themselves, not the parameters
as passed to the script.
An example shows this more clearly:
#!/bin/sh
myfunc()
{
echo "\$1 is $1"
echo "\$2 is $2"
# cannot change $1 - we'd have to say:
# 1="Goodbye Cruel"
# which is not a valid syntax. However, we can
# change $a:
a="Goodbye Cruel"
}
### Main script starts here
a=Hello
b=World
myfunc $a $b
echo "a is $a"
echo "b is $b"
This rather cynical function changes $a, so the message "Hello World" becomes "Goodbye Cruel World".
Functions can be recursive - here's a simple example of a factorial function:
#!/bin/sh
factorial()
{
if [ "$1" -gt "1" ]; then
i=`expr $1 - 1`
j=`factorial $i`
k=`expr $1 \* $j`
echo $k
else
echo 1
fi
}
while :
do
echo "Enter a number:"
read x
factorial $x
done
As promised, we will now briefly discuss using libraries between shell scripts. These can also be used to define common variables, as we shall see.
# common.lib
# Note no #!/bin/sh as this should not spawn
# an extra shell. It's not the end of the world
# to have one, but clearer not to.
#
STD_MSG="About to rename some files..."
rename()
{
# expects to be called as: rename .txt .bak
FROM=$1
TO=$2
for i in *$FROM
do
j=`basename $i $FROM`
mv $i ${j}$TO
done
}
#!/bin/sh # function2.sh . ./common.lib echo $STD_MSG rename txt bak
#!/bin/sh # function3.sh . ./common.lib echo $STD_MSG rename html html-bak
Here we see two user shell scripts, function2.sh and
function3.sh, each sourceing the common library
file common.lib, and using variables and functions declared
in that file.
This is nothing too earth-shattering, just an example of how code
reuse can be done in shell programming.
For details about exit codes, see the Exit Codes part of the Hints and
Tips section of the tutorial. For now, though we shall briefly look at the return call.
#!/bin/sh
adduser()
{
USER=$1
PASSWD=$2
shift ; shift
COMMENTS=$@
useradd -c "${COMMENTS}" $USER
if [ "$?" -ne "0" ]; then
echo "Useradd failed"
return 1
fi
passwd $USER $PASSWD
if [ "$?" -ne "0" ]; then
echo "Setting password failed"
return 2
fi
echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}
## Main script starts here
adduser bob letmein Bob Holness from Blockbusters
if [ "$?" -eq "1" ]; then
echo "Something went wrong with useradd"
elif [ "$?" -eq "2" ]; then
echo "Something went wrong with passwd"
else
echo "Bob Holness added to the system."
fi
This script checks the two external calls it makes (useradd and passwd), and lets the user
know if they fail. The function then defines a return code of 1 to indicate any problem with useradd, and 2 to
indicate any problem with passwd. That way, the calling script knows where the problem lay.

My blog has tips about how to use Unix and Linux commands