Notes of Bourne Shell Scripting
This is a brief reading note about the Bourne Shell Scripting from Wikibooks.org.
What’s SH (Bourne Shell)
Right after the Unix was invented, Stephen Bourne set himself to the task and came up with what he called a shell: a small, on-the-fly compiler that could take one command at a time, translate it into the sequence of bits understood by the machine and have that command carried out. We now call this type of program an interpreter, but at the time, the term “shell” was much more common (since it was a shell over the underlying system for the user).
Stephen’s shell was slim, fast, and though a bit unwieldy at times, its power is still the envy of many current operating system command-line interfaces today. Since it was designed by Stephen Bourne, this shell is called the Bourne Shell. The executable is simply called sh and use of this shell in scripting is still so ubiquitous, there isn’t a Unix-based system on this earth that doesn’t offer a shell whose executable can be reached under the name sh.
Improvements in SH
Indeed, requirements of user are growing fast while the original Bourne Shell cannot meet them anymore.
So developers built a lot of shells that can run in sh-like mode, to more closely emulate that very first sh, though most people tend just to run their shells in the default mode, which provides more power than the minimum sh.
Today, we can find bash in many Unix systems, it is a heavily extended form of the Bourne Shell produced by the Free Software Foundation.
Multiprocessing
This book, Bourne Shell Scripting also has a little part about the Unix and multiprocessing. Because the tutorial bases in the Unix, and, Unix Operating System is and always has been a multt-user, multi-processing operating system (this in contrast with other operating systems like macOS and Microsoft’s DOS/Windows operating systems), the book puts this part before telling some real commands.
The multiple tasks feature means two things,
- A child process can never make changes to the operating environment of its parent—it only has access to a copy of that environment;
- If you actually do want to make changes in the environment of your shell (or specifically want to avoid it), you have to know when a command runs as a child process and when it runs within your current shell; you might otherwise pick a variant that has the opposite effect of that which you want.
Variable Expansion
The reason that using a variable is called substitution is that the shell literally replaces each reference to any variable with its value. The simplest way of using a variable is the way we’ve already seen, prepending the variable name with a $
. So for instance:
$ USER=JoeSixpack
$ echo $USER
So, how to use variables? To using variables, we have multiple ways, let’s see a simple example.
Input:
$ ANIMAL=duck
$ echo One $ANIMAL, two $ANIMALs
Output:
duck, two
So what went wrong here? Well, obviously the shell substituted nothing for the ANIMAL
variable, but why? Because with the extra s
the shell thought we were asking for the non-existent ANIMALs
variable. But what gives there? We’ve used variables in the middle of strings before (as in /home/ANIMAL/logs
). But an s
is not a /
: an s
can be a valid part of a variable name, so the shell cannot tell the difference. In cases where you explicitly have to separate the variable from other text, you can use braces:
$ ANIMAL=duck
$ echo One $ANIMAL, two ${ANIMAL}s
And we can use default values while using variables, something like this:
${ <nowiki/>varname [:]-default <nowiki/> }
An example is,
Input:
$ THIS_ONE_SET=Hello
$ echo $THIS_ONE_SET ${THIS_ONE_NOT:-World}
Output:
Hello World
But is you want to do the default assignment while doing the substitution, you can use a more convenient way,
${ <nowiki/>varname [:]=default <nowiki/>}
Here is an example,
Input:
$ echo ${NEWVAR:=newval}
newval
$ echo $NEWVAR
newval
If you want to substitute with value check, you can use,
${ <nowiki/>varname [:]?message <nowiki/>}
Here is an example,
$ echo ${SOMEVAR:?has not been set}
-sh: SOMEVAR: has not been set
$ echo ${SOMEVAR:?}
-sh: SOMEVAR: parameter null or not set
Control Flow
There are some condition expressions, such as file conditions, string conditions and integer conditions.
Integer Conditions
n0 -eq n1
n0 is equal to n1
n0 -ge n1
n0 is greater than or equal to n1
n0 -gt n1
n0 is strictly greater than n1
n0 -le n1
n0 is less than or equal to n1
n0 -lt n1
n0 is strictly less than n1
n0 -ne n1
n0 is not equal to n1
Nor, and and or,
!B
Negation; is true if B is false.
B0 -a B1
And; is true if B0 and B1 are both true.
B0 -o B1
Or; is true if either B0 or B1 is true.
String Conditions,
-n s
s has non-zero length
-z s
s has zero length
s0 = s1
s0 and s1 are identical
s0 != s1
s0 and s1 are different
s
s is not null (often used to check that an environment variable has a value)
With the basic knowledge about the conditions, we can use the if-statement to generate some code,
Input:
rank=captain
if [ "$rank" = colonel ]
then
echo Hannibal Smith
elif [ "$rank" = captain ]
then
echo Howling Mad Murdock
elif [ "$rank" = lieutenant ]
then
echo Templeton Peck
else
echo B.A. Baracus
fi
Output:
Mad Murdock
The case -statement is sort of a special form of the if -statement,specialized in the kind of test demonstrated in the last example: taking a value and comparing it to a fixed set of expected values or patterns. The case -statement is an elegant alternative to a potentially messy if -statement in such a case.
Here is an example,
Input:
rank=captain
case $rank in
colonel) echo Hannibal Smith;;
captain) echo Howling Mad Murdock;;
lieutenant) echo Templeton Peck;;
sergeant) echo B.A. Baracus;;
*) echo OOPS;;
esac
Output:
Mad Murdock
Loops
While loop
The while -statement is the simplest and most straightforward form of repetition statement in Bourne shell. It is also the most general. Its general form is this:
while command-list1
do command-list2
done
An example is,
while [ $counter -lt 10 ]
do
echo $counter
counter=`expr $counter + 1`
done
Until loop
The until -statement is also a repetition statement, but it is sort of the semantic opposite of the while -statement. The general form of the until -statement is,
until command-list1
do command-list2
done
For Loop
for name in w1 w2 ...
do command-list
done
Example,
for myval in Abel Bertha Charlie Delta Easy Fox Gumbo Henry India
do
echo $myval Company
done
File and Streams
In the Unix based operation system, everything in the whole (Unix) universe is a file.
There are several operators built in to the Bourne Shell that relate to redirecting. The most basic and general one is the pipe operator, which we will examine in some detail further on. The others are related to redirecting to file.
Redirecting to file is built straight into the Bourne Shell, through the following operators:
process > data file
- redirect the output of process to the data file; create the file if necessary, overwrite its existing contents otherwise.
process >> data file
- redirect the output of process to the data file; create the file if necessary, append to its existing contents otherwise.
process < data file
- read the contents of the data file and redirect that contents to process as input.
Redirecting standard error (and other streams)
So far we have looked at redirecting the “normal” standard streams associated with files, i.e. the files that you use if everything goes correctly and as planned. But what about that other stream? The one meant for errors? How do we go about redirecting that? For example, if we wanted to redirect error data into a log file.
As an example, consider the ls command. If you run the command ls myfile.txt
, it simply lists the filename myfile.txt
− if that file exists. If the file myfile.txt
does NOT exist, ls
will return an error to the stderr
stream, which by default in Bourne Shell is also connected to your monitor.
Input:
$ ls nosuchfile.txt
$ ls nosuchfile.txt > logfile.txt
Output:
: no such file or directory
In general you see, the shell cannot know: there could be tons of streams connected to any file. And in order to distinguish one from the other each stream connected to a file has a number associated with it: by convention 0 is the standard in, 1 is the standard out, 2 is standard error and any other streams have numbers counting on from there.
Here is an example,
$ ls nosuchfile.txt 2> logfile.txt
No output to the screen, but if we examine logfile.txt
:
$ cat logfile.txt
: no such file or directory
If we want to redirect stdout and stderr to the same file, we can use the file descriptor as well:
$ ls nosuchfile.txt > alloutput.txt 2>&1
Here 2>&1
means something like “redirect stderr to the same file stdout has been redirected to”.
Special Files
We said earlier that the redirect operators discussed so far all redirect to data files. While this is technically true, Unix magic still means that there’s more to it than just that. You see, the Unix file system tends to contain a number of special files called “devices”, by convention collected in the /dev
directory. These device files include the files that represent your hard drive, DVD player, USB stick and so on. They also include some special files, like /dev/null
(also known as the bit bucket; anything you write to this file is discarded).
As an example of how you might actually use a device file, in the Solaris
flavour of Unix the loudspeaker and its microphone can be accessed by the file /dev/audio
. So:
$ cat /tmp/audio.au > /dev/audio
Will play a sound, whereas:
$ cat < /dev/audio > /tmp/mysound.au
Will record a sound.(you will need to CTRL-C this to finish…)