Introduction to the Command Line

Scripting

If you have a collection of commands you'd like to run together, you can combine them in a script and run them all at once. You can also pass arguments to the script so that it can operate on different files or other input.

Like an actor reading a movie script, the computer runs each command in your shell script, without waiting for you to whisper the next line in its ear. A script is a handy way to:

  • Save yourself typing on a group of commands you often run together.
  • Remember complicated commands, so you don't have to look up, or risk forgetting, the particular syntax each time you use it.
  • Use control structures, like loops and case statements, to allow your scripts to do complex jobs. Writing these structures into a script can make them more convenient to type and easier to read. 

Let's say you often have collections of images (say, from a digital camera) that you would like to make thumbnails of. Instead of opening hundreds of images in your image editor, you choose to do the job quickly from the command line. And because you may need to do this same job in the future, you might write a script. This way, the job of making thumbnails takes you only two commands:

$ cd images/digital_camera/vacation_pictures_March_2009
$ make_thumbnails.sh

The second command, make_thumbnails.sh, is the script that does the job. You have made it previously and it resides in a directory on your search path. It might look something like this:

#!/bin/bash
# This makes a directory containing thumnails of all the jpegs in the current dir.
mkdir thumbnails
cp *.jpg thumbnails
cd thumbnails
mogrify -resize 400x300 *.jpg

If the first line begins with #! (pronounced "shee-bang"), it tells the kernel what interpreter is to be used. (Since bash is usually the default, you might omit this line). After this you should put in some comments about what the script is for and how to use it . Scripts without clear and complete usage instructions often do not "work right". For bash, comments start with the hash (#) character and may be on the ends of executable lines.

The file includes commands conforming to the syntax of the interpreter. We've seen three of them before: mkdir, cp, and cd. The last command, mogrify, is a program that can resize images (and do a lot of other things besides). Read its manual page to learn more about it.

Making scripts executable

To write a script like the one we've shown, open your favorite text editor and type in the commands you would like to run. For bash, you can put multiple commands on a single line so long as you put a semi-colon after each command so the shell knows a new command is starting.

Save the script. One common convention for bash is to use the .sh extension -- for example, make_thumbnails.sh.

There is one more step before you can run the script: it has to be executable. Remember from the section on permissions that executability is one of the permissions a file can have, so you can make your script executable by granting the execute (x) permission. The following command allows any user to execute the script:

$ chmod +x make_thumbnails.sh

Because you're probably planning to use the script often, you'll find it worthwhile to check your PATH and add the script to one of the directories in it (for instance, /home/jdoe/bin is an easy choice given the PATH shown here).

$ echo $PATH
/usr/bin:/usr/local/bin:/home/jdoe/bin

For simple testing, if you're in the directory that contains the script, you can run it like this:

$ ./make_thumbnails.sh

Why do you need the preceding ./ path? Because most users don't have the current directory in their PATH environment variables. You can add it, but some users consider that a security risk.

Finally, you can also execute a script, even without its execute bit set, by passing it as an argument to the command interpreter, thusly:

$ bash make_thumbnails.sh

More control

To provide the flexibility you want, the bash shell and many other interpreters let you make choices in a script and run things repeatedly on a variety of inputs. In that regard, the shell is actually a programming language, and a nice way to get used to using the powerful features a programming language provides. We'll get you started here and show you the kinds of control the bash shell provides through compound statements.

if

This statement was already introduced in the section on checking for errors, but we'll review it here. if is more or less what you'd expect, though its syntax is quite a bit different from its use in most other languages. It follows this form:

if [ test-condition ]
then
  do-something
else
  do-something-else
fi

You read that right: the block must be terminated with the keyword fi.  (It's one of the things that makes using if fun.) The else portion is optional. Make sure to leave spaces around the opening and closing brackets; otherwise if reports a syntax error.

For example, if you need to check to see if you can read a file, you could write a chunk like this:

if [ -r /home/joe/secretdata.txt ]
then
    echo "You can read the file"
else
    echo "You can't read that file!"
fi

if accepts a wide variety of tests. You can put any set of commands as the test-condition, but most if statements use the tests provided by the square bracket syntax. These are actually just a synonym for a command named test. So the first line of the preceding example could just as well have been written as follows.

if test -r /home/joe/secretdata.txt

You can find out more about tests such as -r in the manual page for test. All the test operators can be used with square brackets, as we have.

Some useful test operators are:

-r File is readable
-x File is executable
-e File exists
-d File exists and is a directory

There are many, many more of them, and you can even test for multiple conditions at once. See the the manual page for test.

while (and until)

while is a loop control structure. It keeps cycling through until its test condition is no longer true. It takes the following form:

while test-condition
do
  step1
  step2
  ...
done

You can also create loops that run until they are interrupted by the user. For example, this is one way (though not necessarily the best one) to look at who is logged into your system once every 30 seconds:

while true
do
    who
    sleep 30
done

This is inelegant because the user has to press Ctrl + c or kill it in some other way. You can write a loop that ends when it encounters a condition by using the break command. For instance the following script uses the read command (quite useful in interactive scripts) to read a line of input from the user. We store the input in a variable named userinput and check it in the next line. The script uses another compound command we've already seen, if, within the while block, which allows us to decide whether to finish the while block. The break command ends the while block and continues with the rest of the script (not shown here). Notice that we use two tests through -o, which means "or". The user can enter Q in either lowercase or uppercase to quit.

while true
do
  echo "Enter input to process (enter Q to quit)"
  read userinput

  if [ $userinput == "q" -o $userinput == "Q" ]
  then
    break
  fi

  process input...

done

until works exactly the same way, except that the loop runs until the test condition becomes true.

case

case is a way for a script to respond to a set of test conditions. It works similarly to case statements in other programming languages, though it has its own peculiar syntax, which is best illustrated through an example.

user=`whoami` # puts the username of the user executing the script
              # into the $user variable.
case $user in
    joe)
        echo "Hello Joe. I know you'd like to know what time it is, so I'll show you below."
        date
        ;;
    amy)
        echo "Good day, Amy. Here's your todo list."
        cat /home/amy/amy-todo.txt
        ;;
    sam|tex)
        echo "Hi fella. Don't forget to watch the system load. The current system load is:"
        uptime
        ;;
    *)
        echo "Welcome, whoever else you are. Get to work now, please."
        ;;
esac

Each case must be followed by the ) character, then a newline, then the list of steps to take, then a double semicolon (;;). The "*)" condition is a catchall, similar to the default keyword in some languages' case structures. If no other cases match, the shell executes this list of statements. Finally, the keyword esac ends the case statement. In the example shown, note the case that matches either the string "sam" or "tex". 

for

for is a useful way of iterating through items in a list. It can be any list of strings, but it's particularly useful for iterating through a file list. The following example iterates through all of the files in the directory myfiles and creates a backup file for each one. (It would choke on any directories, but let's keep the example simple and not test for whether the file is a directory.) ⁞

for filename in myfiles/*
do
    cp $filename $filename.bak
done

As with any command that sets a variable, the first line of the for block sets the variable called filename without a dollar sign.

There's another variety of for, which is similar to the for construct used in other languages, but which is used less in shell scripting than it's used in other languages, partially because the syntax for incrementing and decrementing variables in the shell is not entirely straightforward.

parallel

parallel is a useful way of iterating through items in a list while maximizing the use of your computer by running jobs in parallel. The following example iterates through all of the files in the directory myfiles and creates a backup file for each one replacing the extension with .bak. (It would choke on any directories, but let's keep the example simple and not test for whether the file is a directory.)

ls myfiles/* | parallel cp {} {.}.bak

parallel can often be used instead of for loops and while read loops, make these run faster by running them in parallel and make the code easier to read.

parallel can be used for a lot of more advanced features. You can watch an intro video here: http://www.youtube.com/watch?v=OpaiGYxkSuQ