Bash Shell Scripts
Introduction
The Bash shell allows users to interact with the computer through a terminal window, organizing commands that tie various programs together. First developed by Brian Fox in 1987, the Bash shell seeks to incorporate numerous features from earlier shells for the Unix operating system. In particular, the Bash shell draws upon the Bourne shell (developed by Ken Thompson and then Stephen Bourne around 1977), the Korn shell (developed by David Korn in the early 1980s), and the C shell (developed by Bill Joy, based on Thompson's earlier shells).
Within a terminal window, you already have used several commands within the Bash shell. For example, the Basic Linux Commands reading discussed Bash commands, such as:
passwordsleep, history, catpwd, cd, ls, ls -l, which, whereis-
mkdir, rmdir, rm, cp, mv, more, head, pushd, popd manlpr, a2ps, lpq, lprmchmod
In this lab, we consider several additional commands, and we explore how various capabilities can be packaged to accomplish various tasks. The approach is not unlike programming in C (or Scheme), although the programming elements often are entire programs.
Some Additional Commands
Bash includes a huge range of commands. Here are a few more useful capabilities:
| Command | Description | Example |
|---|---|---|
cal |
display a calendar for a given year | cal 2010 |
date |
print the current time and date in various formats many options documented with man date |
date +'%I:%M %p on %A, %B %d, %Y' |
diff |
display differences in lines between two files | diff file1 file2 |
echo |
print specified text | echo hello world! |
env |
print the environmental variables currently set | env |
grep |
scan a file or other input for a specified text | env | grep home |
hostname |
return name of current workstation | hostname |
sort |
sort lines of a file | sort -n -k 12 (sort by numbers in column 12) |
users |
simple list of users | users |
who |
print list of users currently logged in | who and who -a |
whoami |
print the username of the person logged in | whoami |
ypcat password |
print user information from the password file | ypcat password | grep $USERNAME |
More information on each of these commands may be found with
the man facility (e.g., man date).
Beyond this basic documentation included here an in previous labs, many
resources are available on line. Here are a few places to begin:
- The Bash home page from the GNU Project provides a complete online manual for Bash.
- The Bash Guide for Beginners from The Linux Documentation Project provides a good introduction to the Bash shell.
- At this writing, a Google search of "bash shell introduction" produced about 2,5000,000 hits, so there is much easily-accessible material to read on this subject! (Though the quality surely varies.)
Bash Script Files
A bash script is a file containing bash commands. To make the file executable, we must first take the following two steps.
-
The script file should contain the following code as its very first line:
This specifies which program (bash) should be used to interpret the commands in the script.#!/bin/bash -
The file permissions for the script should be set such that is it
executable. Recall that this is typically done as follows:
chmod 755 scriptname.sh
The script can then be run like any other executable file:
./scriptname.sh
Actually, we can also run a script as follows (even without making the script executable), but this is less frequently done.
bash scriptname.sh
Script variables
Variables can be created and initialized as shown below. Note that there should NOT be a space between the variable name and the assignment operator. (If the variable name has a space after it, bash will recognize it as a token, but since the variable does not yet exist, bash will assume the token is command name. It will then complain about not finding such a command.)
varname=12
To access the value in a variable, prepend a $ to the variable name. For example,
echo "Value in varname is $varname."
Arithmetic Expressions Require (( ))
The variables in a bash script are by default interpreted as text. To
get bash to interpret a value as a number, in order to do arithmetic
operations including arithmetic comparisons, you must wrap the
expression in double-parentheses. For example, to increment a
variable i by one, use the
expression((i++)) .
Conditionals and Loops
In addition to basic commands, the Bash shell includes capabilities
for conditionals (if) and loops (i.e., fora and
while).
If statements
One general syntax for an if-statement follows. As you would probably
expect, the elif and else clauses are
optional, and the conditions and commands should be replaced with
meaningful expressions. Note that the spaces that separate the
square brackets from the conditions ARE required.
if [ condition ]; then
command(s)
elif [ condition ]; then
command(s)
else
command(s)
fi
The long history of Bash is particularly evident in
the syntax allowed for if expressions. Bash allows
both, but interprets each syntax properly — but do not try to
mix and match the different versions. The following examples
illustrate acceptable Bash syntax, following two different ancestors.
| Task | Bourne shell style | Korn shell style |
|---|---|---|
numeric testv > 0 |
if [ $v -gt 0 ]; then
|
if (( $v > 0 )); then
|
numeric testv ≤ 0 |
if [ $v -le 0 ]; then
|
if (( $v <= 0 )); then
|
numeric testv < 0 and v == 0 |
if [ $v -lt 0 ]; then
|
if (( $v <= 0 )); then
|
numeric test0 ≤ v ≤ 10 |
if [ 0 -le $v -a $v -le 10 ]; then
|
if (( (0 <= $v) && ($v <= 10) )); then
|
For reference, all of these code segments are available in the Bash shell
bash-example-1.sh
Comparison Operators
Comparison operators are available in both the Bourne and Korn environments, although the syntax is different:
Korn shell: == != < <= > >= && || Bourne shell: -eq -ne -lt -le -gt -ge -a -o
String comparison is possible only with the symbols
== != < <= > >= && ||
Conditionals Regarding Files and Directories
Bash scripts are frequently written for tasks involving the creation and maintenance of files and directories because it can be much faster and easier to write a script for these tasks than to write the corresponding C programs.
There are many operators that can be used for testing conditions that
involve files: whether they exist, what kind of file they are,
etc. Here are a few. Note that you could also place the NOT operator
(!) before any of these tests to test whether the stated
condition is false.
| condition | checks whether... |
|---|---|
-f file |
file exists and is a regular file |
-d dir |
dir exists and is a directory |
-x dir |
file exists and is executable |
For example, you might write:
clist="classlist.txt"
if [ -f $clist ]; then
echo File $clist exists.
else
echo File $clist does not exist.
fi
for loops
The syntax for a for-loop follows.
for varname in value1 value2 ...
do
command(s)
done
The variable varname will take on each value in the list of values
in turn, with one iteration of the loop occuring for each value.
while loops
The syntax for a while loop follows.
while test
do
command(s)
done
Getting Input
Getting Input From stdin
The command read varname will read a value from stdin
(i.e., the terminal) and assign it to a variable
called varname. If the variable does not yet exist, it
will be created.
Note that the option -n can be used
with echo to cause it to not output a newline
character. (This is nice when printing user prompts, so the user can
enter a reponse on the same line as the prompt).
Getting Input From Commands
Sometimes one wishes to execute a bash shell command or other program and store the result in a variable. This can be done by using the tick quote, which is under the tilde key, ~ and looks like this: `. Note now it is different from an apostrophe (near the enter key), which looks like '.
As an example, one might store the number of words in a file to a variable with the following command
numWords=`wc -w`
Getting Input From Command-line Arguments
As we'll see soon, the mechanism for accessing command-line arguments in a bash script differs substantially from C. In bash, there are several variables that are automatically defined and loaded with information from the command-line as shown below.
| variable | stores... |
|---|---|
$# |
number of arguments given |
$* |
list of all arguments given |
$0 |
the name of the script |
$1 |
the first argument |
$2 |
the second argument |
etc |
For example, to verify the number of arguments given you might say:
if [ $# == 3 ]
Redirection and Piping
Some of the most powerful aspects of shell use and scripting are the abilities to redirect input or output and chain commands together with pipes. We look at each of these capabilities in turn.
I/O Redirection
Redirection allows your program to get its input from a file, rather
than the terminal (called standard input or stdin), and
to write its output to a file, rather than the terminal (standard
output, or stdout).
To take the list of commands from a file, rather than
the terminal, one uses the input redirection
operator <. To direct the standard output to a file
one uses the output redirection operator >. Here are
three examples; the first uses input redirection, the second uses
output redirection, and the third uses both.
command < input.txt command > output.txt command < other_input.txt > other_output.txt
The file names are determined by your particular circumstances, and could be anything (though of course the input file specified must exist). Note that output redirection will overwrite any existing file of the name you specify. You can append (rather than replace) an existing file by using the output operator with two angle brackets:
command >> appended_output.txtAs an example, one might use this to keep track of the time to run a program on input with an increasing size, as follows:
for ((inputSize=1 ; inputSize<=1024 ; inputSize=2*inputSize))
do
echo -n "$inputSize " >> data.txt
./runMyTest $inputSize >> data.txt
done
Pipes
The output of one program may be connected as the input of another program via the use of "pipes." This fabulous aspect of the shell scripting environment makes it possible to combine small, modular programs into larger computations that give very specific results from a few general building blocks. As an example, the following command lists all the users running processes on a machine.
ps au | cut -d\ -f 1 | sort | uniq
USER dan dols interiot pergamon root weinman www-data
In this example, the ps command gives us lots of
information on all the processes running on the machine. The output
from ps is piped to cut, which (in this
example) extracts the first field (the username) as separated by
spaces of each line. That output is in turn piped
into sort, which rearranges the entries into
lexicographic order. Finally, this sorted list is processed
by uniq so that all repeated values are removed, leaving
us only with a unique list of active users.
