Create a simple Bourne shell script
Author: Gregory Nou gregoryDOTnouATgmailDOTcom FreeBSD
Reviewer: name contact BSD flavour
Reviewer: name contact BSD flavour
Concept
Most system administration tasks can be automated with shell scripts. Be aware of the advantages and disadvantages of using a Bourne shell(((Bourne shell))) script rather than a csh(1) or bash(1) shell script. Be able to recognize a shebang(((shebang))), comments, positional parameters and special parameters, wildcards, the proper use of quotes and backslashes and: for(((for))), while(((while))), if(((if))), case(((case))), and exec(((exec))). In addition, know how to make a script(((scripts))) executable and how to troubleshoot a script.
Introduction
Choosing a shell interpreter: the Shebang thing
A shell script is a kind of executable that has to be interpreted. Even if computers may be seen as more and more clever, they cannot (for now) deduce from the content of a file set as executable the language of the script, nor can they deduce the type of the shell needed (sh syntax is slightly different from csh syntax). That's why you must declare the interpreter your script will use. To do that, your script must always begin with the line :
#!/path/to/interpreter
For example :
#!/bin/sh
or
#!/usr/local/bin/zsh
But you may also encounter awk, python, perl, ...
In free software, choice matters. You may think bash is a killer app that everybody needs, but it is a fact that it is not shipped with all BSD (at least FreeBSD).
- FreeBSD ships with /bin/sh, /bin/csh, /bin/tcsh
- OpenBSD ships with /bin/sh and /bin/ksh (these are both actually pdksh, the public domain Korn shell), and /bin/csh
- TODO: I don't know the policy on this topic of other BSDs
So what happens when you launch a script? You always launch a script from the command line, so you're basically using your shell interpreter (say, sh). It will look at the file, to guess if it is a well-known executable. If not, it will parse the first line of your script, thinking that it may find the shebang and the interpreter needed. The shebang is always the first line.
Why you would want to use sh? (and the drawbacks of such a decision)
The bourne shell was written by Stephen R. Bourne. It was designed to replace the Thompson Shell. Why would you want to use this shell?
- It's a standard: every BSD has it, at the same location : /bin/sh
- It's simple: most *sh add lot of stuff you don't really need, and which is incompatible with all other. sh is a kind of subset, common to all other (except csh)
- There is a lot of documentation and examples available
What are the drawbacks ? what are the disadvantages against a csh or a bash script ?
(Actually, I don't see any. Maybe less functionalities that bash. But bash has less functionality than zsh. Csh/tcsh also may seem more easy to learn, as it is close to C, but full of bugs)
Core shell programming
Parameters
Parameters are really easy to get in shell scripting. Arguments are numbered beginning from 0, and are referred by $n, $0 being the script name, and $1 the first argument. As an example, consider this script:
#/bin/sh
echo "command: " $0
echo "first argument: " $1
Guess what the print is ;)
Other possibilities you may want to know are $#, which print the number of parameters and $*, which will list all parameters
Variables
Parameters are only a special type of variables, in the sense that they are defined at the very beginning, thanks to command line. So you may guess that variables will have the same syntax than parameters: $something is a variable named "something".
However, to affect a variable, the syntax is slightly different: something=foo without spaces before and after the sign equals. Be aware that writing "=foo" will affect the value "foo" to $something. However, if you want the value of $something to be the value of foo, you will have to put $foo. Also remember that it is not a pointer: affecting 2 to $foo, then $foo to $something and then 3 to $foo will give the following result: $foo = 3 and $something = 2.
You may sometimes need to make some formatted output: for example, if you have $a = var, and you want to write "varb", writing $ab won't do it... In this case, you'll have to write ${a}b.
You may have to void some characters, like if you want to print "\n" without making a return to new line. This is the same as all other languages: \ will void next character, "..." will void everything but \ $ and , '...' will void everything and
...` will interpret the inside.
Finally, two last special variables may be useful: $$ is the PID of the last command, and $? is its status.
Testing a value
Before learning more on conditional branching and loops, you may want to know more on tests. For example, how do we know that a specific file exists?
This is done by using the command "test expr" or "[ expr ]" (be careful: the spaces are needed), where expr can be:
- -r file : true if file exists and is readable
- -w file : true if file exists and is writable
- -x file : true if file exists and is executable
- -f file : true if file exists and is a regular file
- -d file : true if file exists and is a directory
- -s file : true if file exists and size is not 0
- -H file : true if file exists and is an hidden directory
- -h file : true if file exists and is a symbolic link
- s1 = s2 : true if s1 equals s2
- s1!= s2 : true if s1 different of s2
- s1 : true if s1 is not null
- s1 -eq s2 : true if s1 equals s2, algebrically
- s1 -neq s2 : true if s1 is not equal to s2, algebraically
- s1 -gt s2 : true if s1 is greater than s2, algebraically
- s1 -ge s2 : true if s1 is equal or greater than s2, algebraically
- s1 -lt s2 : true if s1 is lesser than s2, algebraically
- s1 -le s2 : true if s1 is equal or lesser than s2, algebraically
Shell script also provides "non" (!), logical and (-a) and logical or (-o)
Usually, true is 0...
Conditional branching
Sh has 2 kinds of conditional branching: if and case
Here is the structure of if...
if cmd
then
list_commands
[elif list_commands
then
list_commands] ...
[else list_commands]
fi
If you're familiar with programming languages, be careful here. The "parameter" for if is a command, and this has a few implications: first, usually, a success of the command would yield a "true", but you have to make sure of that by reading the man. Second, putting "!cmd" won't be true if the command fails: the ! is for the result of the command, not it's status.
The "if" branching begins with keyword "if", and ends with keyword "fi".
Here is an example of the case:
case <parameter> in
<choice1> ) cmd ;;
<choice2> ) cmd ;;
...
esac
where is a variable or a parameter, like $1 and is a event that will trigger the answer. For example, "-vv ) echo "verbose mode";;" would print "verbose mode" if the first argument is $1 if "-vv".
You may want to catch every uncatch possibility by adding a choice "*" (the wildcard). Also, do not forget the esac at the end.
Loops
Sh has three kinds of loops: for, while, until.
Here is the syntax of for:
for <parameter> [in list]
do
list_commands
done
If a list is provided, the parameter will take the values in the list. Else, it will use the command-line parameters as the list.
The while syntax is:
while command
do
list_commands
done
and loop while command status is true.
Finally, the until loop:
until command
do
list_commands
done
Functions
Defining a function is done by specifying a name, ending with parenthesis, and putting the code of the function between { and }. For example:
sayHelloWorld() {echo "Hello World 1!";echo "Hello World 2!"}
or
sayHelloWorld() {
echo "Hello World 1!"
echo "Hello World 2!"
}
Now, you can call your function by putting its name (without $ and ()).
Parameters for functions work exactly as parameters for scripts:
sayHello() {echo "hello " $1}
and calling
sayHello $2
would say hello to the person whose name is the second argument in the command line.
Misc
wildcards: the star . It will replace any number of any characters, e.g. ".txt" will match any file whose name ends by the substring ".txt". For example, to copy all the .sh files from /tmp to the /root/ directory : "cp /tmp/*.sh /root/"
comments: comments are the lines beginning with the symbol # (exception is made of the shebang, in the first line)
If you have to do some maths, expr will make them for you. Syntax :
expr exp1 op exp2
For example:
expr $a + $b
There is a few op possible, like \*, /, %, =, \>...
It is also possible to do other things:
- expr exp1 : exp2 : return the number of common chars
- expr lenght exp : return the length of exp
- expr substr exp n1 n2 : extract a substring of exp, at offset n1 and of length n2
- expr index exp char : return position of char in exp
Practice Exercises
#!/bin/sh
echo "Hello World\n"
Will print "Hello World"
Now, you may want to try to write a script which says "hello bill" if the first argument is bill, else say hello world. (hint: think of case)
You may want to make a program that will print the number of word of each file in a directory (remember that ls
would actually perform an ls, so you may want to use it as the list in a for loop)
More information
sh(1), chmod(1)