Computer Basics
Bash Shell Scripting

Bash Shell Scripting Tutorial

Basics

This tutorial assumes that you already know how to log in to your UNIX machine, bring up the bash shell, and run basic commands such as ls and cat. Getting to this point is fairly easy, but unfortunately this is the level that most users stay at indefinitely. This tutorial is intended to help you start to learn the more advanced features of using a shell, and specifically bash, one of the most powerful shells available.

Please note that most of the command themselves are not explained; you can examine their functionality yourself by either reading their man page, or just experimenting with them to see what they do.

Redirection

Normally programs take input from our keyboard, and display the output to our screens. However, these are just the defaults - UNIX has the ability to redirect the input (commonly referred to as stdin, short for standard input) and output (commonly referred to as stdout, short for standard output).

Here is a simple example: the cat command displays the contents of a file to the screen. But we can redirect those contents to a file using the redirection operator '>', like so:

$ cat myfile.txt
This is the contents of the file myfile.txt
$ cat myfile.txt > newfile.txt
$ cat newfile.txt
This is the contents of the file myfile.txt
$

In effect, we've made cat do the same thing as cp, by redirecting the output from the screen to a file. This isn't terribly useful, but consider another, similar, scenario: cat can take multiple arguments, and it will display the files one after the other. This can be used to append one file onto another and create a new, combined file.

$ cat file1.txt
The quick brown fox...
$ cat file2.txt
...jumped over the lazy dog.
$ cat file1.txt file2.txt > combined.txt
$ cat combined.txt
The quick brown fox...
...jumped over the lazy dog.

Neat, eh?

We can also redirect the input, so that a program takes the contents of a file as if it were typed at your keyboard. cat doesn't take any input, so let's use bc, a command line calculator. Normally, you run the program, and it lets you type in calculations such as "2+2", and then displays the result. But if you have a file which already contains the calculations, you can send it straight to bc - faster and more powerful than cutting-and-pasting the text in with your mouse.

$ cat calc.txt
2+2
$ bc < calc.txt
4
$

A final note: you can use two output redirect symbols together - >> - to indicate that you want to append the file, not overwrite it. Hence:
$ cat file1.txt
Contents of file1.
$ cat file2.txt
Contents of file2.txt.
$ cat file2.txt >> file1.txt
$ cat file1.txt
Contents of file1.
Contents of file2.txt.
$

Pipes

Pipes are similar to redirection, but they are even more powerful, because they allow you to send input and output back and forth between programs. For example, let's say that you want to send the calculation "2+2" to bc in a single command, without creating a file for redirection, or firing up bc and then entering the calculation. You can use the command echo, which normally sends output to the screen:
$ echo 2+2
2+2
$

But instead we can send it through a pipe using the pipe character '|', which you create by typing shift-backslash on your keyboard.

$ echo 2+2 | bc
4
$

One thing that may throw you off about this example is that it seems intuitively out of order; bc is the "important" program being run, and normally we would expect that to come first. But it doesn't, and for good reason: the pipeline works by sending the output of each successive program to the one immediately to its right. In this case, echo has to run first, because it generates the text "2+2" and stuffs it into the pipe. bc then receives the "2+2" text, and does its thing, which is process the calculation.

Possibly one of the most immediately useful features of the UNIX command line is the ability to fire off an email instantaneously using the mail command.

$ echo "Hey, I sent this mail from the bash command line!" | mail -s "bash rocks" user@host.com

Combinations

The true power of UNIX shells, and especially bash, is reflected in your ability to mix and match redirection, pipes, and other operators almost limitlessly. Here's a combination example: we send 2+2 through the pipe, which bc calculates, and then sends the output to a file.

$ echo 2+2 | bc > result.txt
$ cat result.txt
4
$

Variables

Variables are values that you give a name. They are significant because you can call them by that name throughout your bash "code", but it can contain different values at different times. A simpler way to think about them is that they are simply storage containers, for keeping data around that you will later use within bash.

Here is an example of assigning a value, and then displaying its contents:

$ MYVAR=3
$ echo $MYVAR
3
$

When the variable is first assigned (and created), the $ symbol is not used. From that point forward, however, it must be prefixed with the $ symbol or it will not be properly recognized by bash.

Variables can contain text, numbers, lists - just about anything that you can think of. There is no "type casting" as in structured programming languages, so you need not keep track of what sort of data is stored where.

It's possible to do basic math with variables, like so:

$ A=2
$ B=3
$ C=$((A+B))
$ echo $C
5

A simple use for variables is to save some data temporarily that you're going to use in a later command, possibly several commands. In this example, we'll get a list of files, print each one, append them to a master file, and then delete them.

$ FILES=*.txt
$ lpr $FILES
$ cat $FILES >> master.txt
$ rm $FILES

So far variables might not seem terribly useful. In fact, their power doesn't become obvious until we start using constructs such as loops.

Loops

Computers are excellent at repetitious tasks. Yet all too often, because of limited user interfaces (especially the GUI interfaces that are the most popular), computer users find themselves doing mindless tasks, such as renaming a long list of files. This is a task that is ideal for an iterative loop. It is "iterative" because we step through each item in a list, doing a similar operation on each one. The two main types of loops are for loops and while loops, but let's focus on for loops as they are generally more useful at the command line.

The format of the for loop command is as follows:

for counter in list; do command; done
Everything in italics are items you need to replace with your own values. Counter is the name of the variable you want to use to count with; it can be any single word. List is a list of items, one after the other, that you wish to step through, one by one. And command is what you actually want to do to each one. Since an example is worth a thousand words, take a look:

$ for i in 1 2 3; do echo Loop iteration: $i; done
Loop iteration: 1
Loop iteration: 2
Loop iteration: 3
$
The counter variable is called "i", and you may note that when referring to it in places other than right after the for we place a $ character in front of its name, so it becomes $i.

The list is 1, 2, and 3. List items should be separated by spaces.

The command is "echo Loop iteration: $i". This command gets run for each member of the list; and the counter variable ($i) gets replaced with the current list member we are executing on each time we step through it. Thus, the first time it prints "Loop iteration: 1" because the value of $i is 1.

One neat trick we can use is to use wildcard characters (* and ?) in order to loop through files in the current directory. As a simple example, we could simply print each filename, duplicating the effect of the command ls:

$ ls
file1.txt file2.txt
$ for i in *; do echo $i; done
file1.txt
file2.txt
$

That's not so useful, but let's say that we want to rename each file to start with the word "my".

$ ls
file1.txt file2.txt
$ for i in *; do mv $i my-$i; done
$ ls
my-file1.txt my-file2.txt
$

Backticks

The magical backticks (the character `) are one of the best kept secrets of shell scripting, but at the same time one of the most useful. They allow you to run a secondary command inside of a new shell, and the output from that command will be placed onto the command line on the spot! As usual, and example shows best.

$ cat masterfile.txt
file1.txt
file2.txt
file3.txt
$ cat `grep -l 2 masterfile.txt`
This is the contents of file 2.
$

In this example, the result of the command "grep -l 2 masterfile.txt" is going to be "file2.txt". Normally that would just be displayed to the screen, and at that point you could manually type "cat file2.txt" to see its contents. But you can do it in a single command with backticks; the output of the grep command ("file2.txt") is substituted on the command line, producing the command "cat file2.txt" for you!

Bringing It All Together

At this point, you need to explore these tools yourself in order to get a better understanding of their great flexibility. Provided below are a number of examples to aid in your exploration of these topics.

Converting a list of filenames into lower case

$ for i in *; do mv $i `echo $i | tr A-Z a-z`; done

Printing out only files which contain the text "PrintMe"
$ lpr `grep -l PrintMe *`

Determining the difference in length (number of lines) between two files
$ echo `cat file1.txt | wc -l` - `cat file2.txt | wc -l` | bc

Sending mail to a list of email addresses in a file
$ for addr in `cat email-addresses.txt`; do cat message.txt | mail -s "Hi there!" $addr; done

Archiving all logfiles to a file named after the current date
$ tar czvf logs-`date +%m%d%y`.tar.gz *.log