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.
Note: Comments are owned by the poster. We are not responsible for their content.
Is the $( ) a bash'ism or is it portable? While I love bash over the other shells available, I always try and write scripts as Bourne compatable as possible (or at least as compatable as I can since I seem to have "misplaced" my Bourne referrence book).
As an SA, I'm not inclined to believe that any one tool is the perfect tool for every single job. In addition, for some applications, I find shell to be cleaner code than perl.
Now, I use perl almost constantly, but my rule for when to use perl is different from yours. I use perl if I'm doing something that does network IO. SNMP scripts, I use PERL. LDAP scripts, I use perl. And, like you, if I'm writing something longer than 50 or so lines of shell, I back up and reevaluate to see if I'm doing something wrong. Usually what I'm doing wrong is not using perl<nobr> <wbr></nobr>;-)
Here's the point: you can't be a one trick pony as an SA. That's why I'm covering more than one language. My last article was an overview of what direction you might go in. This one is about bash, the next one will be about awk. I don't know if I'll write an article on perl, just because there are already zillions of articles on getting started with it.
input on that is hereby solicited.<nobr> <wbr></nobr>:)
On October 18th, 1987, the first version of Perl, Perl 1.0, was posted to the usenet group comp.sources.<nobr> <wbr></nobr>...
By 1991, plenty of interest had grown for Perl, that O'Reilly & Associates published a book name 'Programming Perl',<nobr> <wbr></nobr>... Perl 4.0 is released March 21st of the same year.
I don't doubt that what you write is true. But I doubt it is the case for the vast majority of GNU/Linux sysadmins.
Also, Perl does run on all the OS's you mention above, including Minix<nobr> <wbr></nobr>;-)
It was after 10 years or so of waiting for POSIX scripts to fade out, it not happening, and having to deal with too many scripts written by others including the humongous script such as the kind generated by autoconf, that I bit the bullet and wrote the first usable <A HREF="http://bashdb.sourceforge.net/" title="sourceforge.net">bash debugger</a sourceforge.net>.
These are great additions - thanks for this! More! More!
If there's an interest in taking this article further, let me know that, too, so I can start getting more stuff together<nobr> <wbr></nobr>;-)
I find the <tt>seq</tt> utility really useful for doing enumerated loops. For instance, running <tt>seq 1 4</tt> produces the following output:
You can also do steps, so <tt>seq 1 5 2</tt> produces the following:
So it's easy to do the following. (Notice that the more up-to-date way of inserting a shell command is with <tt>$( )</tt> instead of <tt>` `</tt>.)
If you need to have a special format, <tt>seq</tt> supports some of the <tt>printf</tt> standards. For instance, if I want to make sure all my entries have two digits, I might do this:
...which just goes to show you that there's always more than one way to skin a cat.
For example, create a file called 'func' containing your functions. In your main script, you just have to:
. func (bash, ksh, sh)
or
source func (csh, tcsh)
and all your defined functions are available in the main script.
Another point: you can pass and return values to functions. For example, you want to write messages to a log file. Create a function called 'update_log' as follows:
update_log() {
LOG=/tmp/app.log
MSG=${1-:"No message defined."}
echo $MSG >> $LOG
}
Any time you need to write to your log file, you simply define your message, and call the function.
update_log "This is the message"
To return values from a function, try this: Create a function called 'now'
now() {
DAT=$(date +%m-%d-%H:%M:%S)
}
You can then call the function and echo the variable
now
echo $DAT
06-23-08:34:43
In addition to debugging other's scripts, one can use it to learn more about how bash and POSIX shells work.
Huh...
Posted by: Anonymous Coward on June 23, 2004 04:10 PM#