Shell Scripting Made Easy

Created: December 21, 1999
Last updated: December 21, 1999
Development stage: Alpha

You may have heard that one advantage of Linux is that everything is programmable. You can automate just about any task. There are a lot of ways of doing this. One of the most common is the shell script, which is sort of equivalent to a DOS "batch file".

Simply put, a shell script is a list of commands to run; but it's a lot more flexible than that implies.

If you get into really complicated scripting, especially automatically processing text files, you may be better off learning a scripting language like python or perl or scheme or tcl or...

DISCLAIMER

Several things to watch out for:

  1. The example scripts assume you are running a bourne-compatible shell, specifically bash. They will not work with tcsh or other shells.

  2. The example scripts come with no warranty whatsoever. I will not be held responsible if any of them eat your files, trash your hard drive, etc. I use all these scripts myself, but I make no guarantees.

Index

  1. Overview - how to start writing bash shell scripts
  2. Basic Techniques
  3. Examples - a bunch of my favorite scripts.
  4. Related Books - books to consider for further information

Overview

To make a shell script, do this:
  1. Create a file, let's call it "foo". Put it in a directory in your path. I recommend creating a directory called 'sh' in your home directory as a special place to put your own shell scripts. Add this directory to your path by putting this command in /etc/profile, or in .bash_profile in your home directory: export PATH=$PATH:/home/my-user-name/sh

  2. In the first line of the file, type this: #!/bin/sh

    That's a magic word that tells the system you want to execute the commands in this file using the 'sh' shell, which on most linux systems is actually a link to /bin/bash.

  3. Change the permissions on the file, by giving this command:

    chmod +x foo

    This makes the file "executable"; now you can execute it just by typing its name. For more information see using chmod.

  4. Now put some commands in the file!
As an example, let's take this really simple script:
#!/bin/sh

echo hello world

Make the file executable, then type the file's name as a command. Guess what... it prints 'hello world.'

Basic Techniques

Comments

You can put comments in your scripts, by typing the '#' character. Anything after the # will be ignored. Use this feature; it will help you remember what your script is supposed to do later, and it can help anyone else who uses your script.

Variables

It can confuse you at first, but there's basically only two syntax rules to remember about variables in the bash shell:
  1. to make use of the variable, put a dollar sign in front of the name;
  2. To create the variable, use an equal sign immediately after the variable name (no spaces!), and DON'T use the dollar sign before the name.

Example: Let's create and then print a variable. Try typing this at the shell prompt:

foo='hello' ; echo $foo

Arguments

When you run a shell script, you can give arguments. The arguments are stored in variables called $0, $1, $2, $3, ...

The first one, $0, actually gives you the name of the shell script itself. The rest starting with $1 are the ones you're more likely to use right away. To play with this idea, try this script:

#!/bin/sh
echo $1
echo $2
echo $3

Backquotes

You can get the output of any command just by putting it in backquotes (the backquote key is usually in the upper left of the keyboard).

Example: Type this at the command line: listing=`ls`

Now the variable $listing contains the output of the command ls, namely, a list of files.

if...then

What if you want your script to do different things under different circumstances? Easy, just use an 'if' block, like this:
if some-test-command
then
     do-something
fi
Just replace some-test-command with a command that either succeeds ("returns true") or fails ("returns false"). And replace do-something with a command you actually want to run if the test command returns true!

Notice that you always have to use 'fi' to close the block. ('fi' is 'if' backwards, get it? Ahh, unix humor, ha ha.)

You can also add an else clause, which tells what to do if the test command is false, like this:

if some-test-command
then
     do-something
else
     do-something-else
fi

You can also add an elif clause, which is shorthand for "else if", meaning, "on the other hand, if something else is true...". This lets you test for more than one condition.

if some-test-command
then
     do-something
elif some-other-test-command
then
     do-something-else
else
     do-something-entirely-different
fi
Hopefully this will all become clear in the Examples below.

for...do

for provides a way to do the same action a bunch of times with a bunch of different things. For example, say you have a list of files stored in a variable, and you want to copy all those files. You could do that like this:
files="foo bar baz batz"
for f in $files
do
     cp $f $f.backup
done
Or you could do that all on one line, like this: for f in $files; do cp $f $f.backup ; done

Examples

These are not guaranteed to be great examples, but they should be understandable, and I use them all.

For some really great useful shell scripts, see the Linux Tips-HOWTO, which you may have in /usr/doc/HOWTO/Tips-HOWTO.


Backing Up Configuration Files

#!/bin/sh
# 

if [ ! $1 ]  # This means, "if there's no first argument..."
then
    echo "  "Usage: `basename $0` output.tar.gz
    # note that `basename $0` gives the name of this script!
    echo "    This creates a tarred, gzipped archive of all"
    echo "    the hidden files in your home directory."
    echo "    Warning: if an archive by that name exists,"
    echo "    it will be overwritten."
    exit 1 # Quit this script now!
fi

cd ~ # go home!

rm -f /tmp/skip

# Make a list of files to skip... add to this as you wish.
ls -1d --color=no .gimp/gimpswap* .gimp/tmp >>/tmp/skip
ls -1d --color=no .[a-zA-Z]*~ ~/.[a-zA-Z]*bak .[a-zA-Z]*errors
>>/tmp/skip
ls -1d --color=no .[a-zA-Z0-9_]*/*cache .[a-zA-Z0-9_]*/*tmp >>
/tmp/skip
ls -1d --color=no .[a-zA-Z0-9_]*/*~ >> /tmp/skip
ls -1d --color=no .[a-zA-Z]*/*.bak  >>/tmp/skip
ls -1d --color=no .[a-zA-Z]*/*.BAK  >>/tmp/skip
ls -1d --color=no .[a-zA-Z]*/*.backup  >>/tmp/skip
ls -1d --color=no .[a-zA-Z]*/*.old .[a-zA-Z]*/*.OLD  >>/tmp/skip
echo "core" >> /tmp/skip

# Make the archive, and put it in $OLDPWD, which is the directory we
# were in before that cd command above.
tar -cvz -X /tmp/skip -f $OLDPWD/$1  `ls -1 -d --color=no
.[a-zA-Z]*`

# Now clean up and go back where we were
rm -f /tmp/skip
cd $OLDPWD # go back to where we were before starting the script.

Simple Trash Can

#!/bin/bash
# Simple script to throw stuff in a "trash bin" instead of deleting
it.
# Requires a directory called "trash" in your home directory.
# NOTE: Does not work on filenames with embedded spaces.
if [ ! $1 ] ; then
	echo Usage:
	echo `basename $0` string
	echo For filenames that contain \"string,\"
	echo throws all files \(and their contents, if they are
directories\)
	echo into a \"trash\" directory in your home directory.
	echo Options:
	echo "-u  Disk usage of your trash directory."
	echo "-ls  List files in trash."
	echo "-empty  Delete everything in trash."
	exit 1

elif [ $1 = "-u" ] 2> /dev/null ; then
	echo Disk usage of ~/trash:
	du ~/trash

elif [ $1 = "-empty" ] 2> /dev/null ; then
	echo Emptying trash...
	rm ~/trash/* ; rm ~/trash/.* 2> /dev/null

elif [ $1 = "-ls" ] 2> /dev/null ; then
	ls -a ~/trash
	

else
	for file in *$1* ; do
	mv $file ~/trash
	done
fi

Removing Spaces from Filenames

#!/bin/bash
# Paul Winkler's second script, for removing spaces from files
# that have stupidly been named with them, and substituting
# the usual character "_" ...

# 'case' is another way of testing things. Basically, the next 3
lines
# translate as: "if $1 is '-a' do everything up to the line ';;' ".
case $1
in
  -a)   # If $1 is "-a"...
   for files in *\ * ; do
   echo mv \"$files\" `echo $files | sed s/\ /_/g` | bash
   done
   echo You just removed spaces in all filenames in `pwd`.
   echo They have been replaced with \"_\".
   ;;
  -s)   # If $1 is "-s"...
   for files in *$2* ; do
   echo mv \"$files\" `echo $files | sed s/\ /_/` | bash
   done
   echo You just removed spaces in all filenames containing
   echo \"$2\". They have been replaced with \"_\".
   ;;
  *)   # If $1 is anything else, print instructions!
   echo Usage: \"`basename $0` -a\" to remove spaces from all files
in this directory \(`pwd`\).
   echo \"`basename $0` -s anything\" to remove spaces from files
containing
   echo the string \"anything.\"
   echo 'Spaces will be replaced with an underscore ("_").'
esac # End of the "case" block.


Related Books

  1. Linux in a Nutshell - a good overview of all the most common Linux commands, including a comprehensive section on the bash shell.

Copyright © 1999 Paul Winkler. All rights reserved. Permission to use, distribute, and copy this document is hereby granted. You may modify this document as long as credit to me is given.