Programming with Bash

Face it, you can't become any kind of respectable administrator without knowing a decent bit about shell scripting. You don't have to be the "Bashmeister General" or anything, but having a firm grasp on the basics will get you out of a lot of jams, and provide a foundation for furthering your experience and your skill. In this article we'll cover some programming functionality of the bash shell.

Functions are useful shell programming constructs. In the shell, functions are, for all intents and purposes, the same as methods and subroutines. A function is a way to collect a bunch of bash commands, statements, and expressions into a single box. You label the box, and then, when you need the functionality contained in the box, you call it by name. You can have lots of functions in the same script, as long as you give them each a unique name.

Here's what a very simple bash function looks like. This function moves all files of specified types into designated directories:

function cleanup {
   mv *.{rpm,gz,zip} ~/src/. 2>/dev/null
   mv *.{png,jpg,gif} ~/pics/. 2>/dev/null
}

Once defined, a function can be treated in a script as just another bash command. However, be careful: if you call a function at the top of the script, but don't define it until the bottom, bash will issue a "command not found" error.

Why should I use functions?

No matter what language you wind up scripting in, you'll find that functions are helpful for a couple of different reasons. The first is that they make maintenance and debugging easier. If most of the things you do in your script are wrapped in functions, then your script just consists of calling functions, checking that everything went OK, reporting if something went awry, and then starting over calling the next function. If something does go wrong, you know precisely which chunk of code to look at for the problem. Functions abstract the flow of the code a bit, which makes it much easier for the uninitiated to garner a clue as to what your script does. Once they know the goal of the script, they can look back at the functions to see the implementation.

Another nice thing about functions is that they can be ripped right out of one script and thrown into another, almost untouched. You may need to change a variable here or there, but the main logic is done, which saves you a lot of time when you have to write something new.

Let's get loopy!

As an administrator, you'll often need to perform the same operation on a number of input elements. For example, suppose you're setting up a development environment, and you have to create a code directory for each development team. You lucked out in that all of the development teams are represented in the /etc/group file with the prefix "dev-" -- so you have dev-alfred, dev-barney, dev-charlie, and so on. Here's a quick loop that'll do the work for you:

for GRP in `getent group | cut -d: -f1 | grep ^dev-`; do
    mkdir $GRP
done

This code creates directories (under the current directory, as written) for any groups whose names begin with "dev-". This may not be efficient if you only have two or three groups, but if you have 25, 50, or 100, it's far shorter this way! If you translate line one into English, I'd read it as "run the specified command in the backticks. For each value returned, assign it to the variable GRP and then do the rest." In line two, I take that GRP variable and make a directory named after the group. On line three, I simply put the done statement so that the script knows to start over for any other values of GRP.

This one DOESN'T go to 11

Another type of for loop is one that I call a counter loop. What if there's only one development group, but you want to create a generic directory for each member of the group. If there are 10 people in the group, the directories would be named dev1, dev2, and so on. You need to see how many people are in the group, and then create directories accordingly. To simplify the example, I'll assume you counted the number of group members and found 10. Here's one way to create the necessary directories that'll show you a counter loop in action:

for (( num=1; num <= 10; num++ )); do
    mkdir "dev$num"
done

The first line of code sets the variable num to one, and that's the value of num for the first iteration of the loop. Therefore, the first directory to be created is dev1. Each time through the loop, num is incremented (num++), and checked to see if it is either less than or equal to 10. If it is, the loop runs again. This means that the last directory to be created should be dev10, because on the following loop iteration, num will be 11, which is NOT less than or equal to 10.