Linux.com

Feature: Linux

SysAdmin to SysAdmin: Programming with bash

By Brian Jones on June 23, 2004 (8:00:00 AM)

Share    Print    Comments   

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.

Share    Print    Comments   

Comments

on SysAdmin to SysAdmin: Programming with bash

Note: Comments are owned by the poster. We are not responsible for their content.

Huh...

Posted by: Anonymous Coward on June 23, 2004 04:10 PM
... where's the article?

#

Re:Huh...

Posted by: Anonymous Coward on June 23, 2004 05:11 PM
<A HREF="http://www.linux.com/article.pl?sid=04/06/16/1621204" title="linux.com">here</a linux.com>

#

$ cd /linux/dot/com

Posted by: Don de Los Alamos on June 23, 2004 06:14 PM

 <A HREF="http://www.linux.com/article.pl?sid=04/06/16/1621204" title="linux.com">cat
bj-sysadmin-bash</a linux.com>

#

Re:Other ways to iterate

Posted by: Administrator on June 24, 2004 02:30 AM
> Notice that the more up-to-date way of

> inserting a shell command is with

> $( ) instead of ` `.


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).

#

Re:Other ways to iterate

Posted by: Administrator on June 24, 2004 02:47 AM
It is indeed bash-specific, so when using it, it's a good idea to make your scripts' "magic" header agree (i.e. <tt>#!/bin/bash</tt>). It makes some complex nesting operations easier, and is easier to read. If you're aiming for Bourne compatibility by all means stick with backquotes. I rarely have machines around that don't have bash available, and if so, locating and replacing the shell substitution is easy. (Making those nesting operations work right is another kettle of fish entirely.)

#

perl?

Posted by: Administrator on June 24, 2004 01:21 AM
Not to be a buzzkill here, but just curious. Why don't more sysadmins use perl? It's nearly impossible to install Linux without perl. And bash is a tedious weak scripting language, my theory has always been that if I have to write more than one conditional, or if I need to write a subroutine, then I've already went beyond the boundaries of being able to write it sensibly in bash. That's where perl comes in. Do other's use perl more for their sysadmin scripts? Just curious. thanks

#

Re:perl?

Posted by: njcajun on June 24, 2004 08:33 PM

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>:)

<nobr> <wbr></nobr>/brian

#

Re:perl?

Posted by: Administrator on June 24, 2004 11:24 PM
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>:)




Both would be appreciated, but I'd choose perl--maybe slurp a recurring piece on webpage; daily upload file when mtime newer than backup via rsync module; ping your gateway (cron'd @ 30 min interval), append failure to log, rotate log when it reaches 1 meg || 1 week; tail apache error log and nslookup hostname for offending IP; activate iptables logging, tail messages for DROP, resolve hostname and log + time, port.

Thanks in advance!
/chris

#

Re:perl?

Posted by: Administrator on June 25, 2004 03:55 AM
I've been using shell since before perl existed. My fingers know it. It's on every system I've admin'd or used: SunOS, Solaris, Linux, MacOSX, HP-UX, BSD, Ultrix, OSF/1 (Digital Unix/Tru64 Unix), AIX, CrayOS, Cygwin, Minix<nobr> <wbr></nobr>:-)

Perl isn't available on everything. I've been in environments where I wasn't allowed to install perl either.

Shell works. I don't have to bury my head in the reference manual for a silly little script.

I know how to extend shell quite a bit. Enough that I rarely need perl.

That said, perl rocks. If I need data structures or to do something large, shell often doesn't quite do it.

It's like having a bowie knife vs swiss army. If all you need is a blade the bowie will do.

#

Perl history and OS's it runs on (Was Re:perl?)

Posted by: Administrator on June 26, 2004 09:02 PM
For the record, according to this <A HREF="http://www.pccontrolanywhere.com/perlcentral/history.htm" title="pccontrolanywhere.com"> History of Perl</a pccontrolanywhere.com>:


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>.

#

Re:perl?

Posted by: Administrator on June 24, 2004 01:44 AM
I know many sysadmins that use nothing but Perl for their administrative duties. I, too, use Perl for things that are just too kludgy to do with the shell.

However, there are many installations that just don't have Perl as an option. Specifically, older workstations and servers (of which I administer many) which don't have gcc or Perl, like Sun SparcStations and older HP 700's. If you have ever tried to compile gcc on an HP with the included native compler, you'll know what I'm talking about. This is in addition to the fact that many of these machines don't have the disk space to install Perl.

I'm not trying to bash Perl, it's a wonderful language. But I still continue to be amazed what one can do with the native tools available through the shell.

#

Re:perl?

Posted by: Administrator on June 24, 2004 01:55 AM
Well, we do use perl, a lot; but shell is extremely handy in some situations:

1) (As the other poster said) On non-Linux machines. For example, Solaris 8 was the first solaris to include perl as part of the distro, and even then it wasn't necessarily set up too well. And fuhgeddaboutit if you have old machines to deal with.

2) shell is just sometimes a lot easier. For example, if you need a script to move stuff around and change permissions, or to download a tarball, untar it, compile, install and tweak, you rapidly get very annoyed with perl, because basically you end up using it as a wrapper to a bunch of shell commands. Shell (especially korn and bash) is actually extremely powerful for SA tasks - it just requires a bit more work. And if it gets too much for the shell, you can always just throw it at sed or awk to do some tricky stuff.

Perl is great, but sometimes it's just the wrong tool for the job.

#

Re:unctions in a separate file, passing values

Posted by: njcajun on June 23, 2004 11:12 PM

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>;-)

<nobr> <wbr></nobr>/brian

#

Other ways to iterate

Posted by: Administrator on June 23, 2004 07:50 PM

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:


<tt>1
2
3
4
</tt>

You can also do steps, so <tt>seq 1 5 2</tt> produces the following:


<tt>1
3
5
</tt>

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>.)


<tt>for num in $(seq 1 10); do

    mkdir<nobr> <wbr></nobr>/home/dev$num

done
</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:


<tt>for num in $(seq -f %02g 1 10); do

    rsh node-$num echo hostname   # node-01, node-02...

done
</tt>

...which just goes to show you that there's always more than one way to skin a cat.

#

unctions in a separate file, passing values

Posted by: Administrator on June 23, 2004 10:41 PM
You can also 'source' a file which contains all your functions, much in the same way you source environment variables.


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

#

Advanced Bash-Scripting Guide

Posted by: Administrator on June 25, 2004 12:12 AM
The Advanced Bash-Scripting Guide is your one-stop shop for all sorts of shell tricks and tips; if you have something new, be sure to submit!



Advanced Bash-Scripting Guide

<A HREF="http://www.tldp.org/LDP/abs/html/index.html" title="tldp.org">http://www.tldp.org/LDP/abs/html/index.html</a tldp.org>



-te

#

A debugger for bash

Posted by: Administrator on June 26, 2004 09:08 PM
My <A HREF="http://bashdb.sourceforge.net/" title="sourceforge.net">bash debugger</a sourceforge.net> has been around for a couple of years.


In addition to debugging other's scripts, one can use it to learn more about how bash and POSIX shells work.

#

This story has been archived. Comments can no longer be posted.



 
Tableless layout Validate XHTML 1.0 Strict Validate CSS Powered by Xaraya