The Über-Skeleton Challenge
I received an interesting message from Angela Kahealani with a challenge: "Here's what I'd like to see in Work the Shell: a full-blown shell script template. It should comply with all standards applicable to CLI programs. It should handle logging, piped input, arguments, traps, tempfiles, configuration files and so on." That's an interesting idea, and it fits neatly into something I've been talking about in the last few columns too: the difference between writing something quick and streamlined and writing bulletproof scripts. So let's jump in!
Parsing Command-Line Arguments
The first step of any meaningful shell script is to parse the starting arguments. There's a function built in to Bash for this, but it's rather tricky to work with. For example:
while getopts "ab:c" opt; do
case $opt in
a) echo "-a was specified" ;;
b) echo "arg given to b is $OPTARG" ;;
c) echo "-c was specified" ;;
\?) echo "Invalid option: -$OPTARG" >&2 ;;
esac
done
This specifies that you're going to have three possible parameters: -a,
-b and -c, and that -b has an argument. Using
getopts, they can
occur in any order and can be combined where it makes sense. For example,
-cab arg works fine, with arg being set as the optional
parameter for -b. -abc arg
wouldn't work, however,
because what appears immediately after the b needs to be its
optional parameter.
What's nice about working with getopts is that it does all
the hard work for you—there's no need to worry about shifting twice after an
optional parameter is read and so on. If you give it bad parameters, the
"?"
value will be triggered, with an error output.
Many programs continue to parse input after all the flags have been eaten,
and you'll need code to handle that situation too. The key variable in
this situation is OPTIND, which contains the number of positional parameters
that getopts has processed. The solution looks like this:
shift $((OPTIND-1))
Now $1 is the first non-starting-flag option;
$@ is the full set of
arguments given minus all the starting flags and so on.
Logging Messages
Adding logging to a script actually is quite easy, if you're not going to have a lot of instantiations running simultaneously. You could use syslog, but let's start with the most basic:
if [ $logging ] ; then
echo $(date): Status Message >> $logfile
fi
Or, better, here's a more succinct "date" format and the process ID:
echo $(date '+%F %T') $$: Status Message >> $logfile
In the logfile itself, you'd see something like:
2012-08-07 15:07:56 7026: Status Message
When there's a lot going on, that information will prove invaluable for debugging and analysis.
But what if you did want to use syslog and get the script
messages in the standard system logfile? That can be done with the handy
"logger" program, which has surprisingly few options, none of which
you need.
Instead of the echo statement above, you would simply use:
logger "Status Message"
Check /var/log/system.log, and you can see what has been automatically added:
Aug 7 15:12:26 term01 taylor[7100]: status message
In fact, if you want to be really streamlined, you could have something like this at the top of your über-script:
if [ $logging ] ; then
logger="/usr/bin/logger"
else
logger="echo >/dev/null"
fi
Now every invocation where you'd potentially log information in the
system log will either be the standard
/usr/bin/logger
message or
echo >/dev/null message, the latter causing the information to be
discarded without being displayed or saved.
Trapping Signals
For most shell scripts, a quick ^C kills them and that's that. For other scripts, however, more complicated things are going on, and it's nice to be able to, for example, remove temp files rather than leave detritus all over the filesystem.
The key player in this instance is a program called trap, which takes two parameters, the function (or name of the function) to invoke and the signal or set of signals to associate with that function.
Here's an interesting example:
trap '{ echo "You pressed Ctrl-C" ; exit 1; }' INT
echo "Counting, press Ctrl-C to exit"
for count in 1 2 3 4 5 6 7 8 9 10; do
echo $count; sleep 5
done
If you run this, you'll find that the script will count from 1–10 with a
5-second delay between each digit. At any point, press Ctrl-C and the
trap is triggered; the echo statement is invoked, and the script exits with a
nonzero return code (exit 1).
Sometimes you want to make the script have trap management in certain places, but not others, in which case you can disable it at any time by specifying a null command sequence:
trap '' INT
Easy enough. The code snippet probably would appear similar to:
trap '{ /bin/rm -f $tempfile $temp2; exit 1 }' SIGINT
If you're wondering about the last parameter, it's the signal name.
There are a lot of signals defined in the Linux world, and they're all documented in the signal man page.
The most interesting signals are SIGINT, for program interruptions; SIGQUIT for a program quit request; SIGKILL, the famous "-9" signal that cannot be trapped or ignored and forces an immediate shutdown; SIGALRM, which can be used as a timer to constrain execution time; and SIGTERM, a software-generated termination request.
Let's take a closer look at SIGALRM, as it's darn useful for situations when you're concerned that a portion of your script could run forever.
To set the timer, use trap, as usual:
trap '{ echo ran out of time ; exit 1 }' SIGALRM
Then elsewhere in the script, prior to actually invoking the section that you fear might take too long, add something like this:
(
sleep $delay ; kill -s SIGALRM $$
)&
That'll spawn a subshell that waits the specified number of seconds then
sends the SIGALRM signal to the parent process (that's what the
$$ specifies, recall).
Next month, I'll continue this interesting project by showing an example of the SIGALRM code and adding some additional smarts to the script, including the ability to test and change its behavior based on whether it's receiving input from the terminal (command line) or from a redirected file/pipe.
Any other fancy tricks you'd like it to do? E-mail me via http://www.linuxjournal.com/contact.
Keyboard photo via Shutterstock.com
Dave Taylor has been hacking shell scripts for over thirty years. Really. He's the author of the popular "Wicked Cool Shell Scripts" and can be found on Twitter as @DaveTaylor and more generally at www.DaveTaylorOnline.com.
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Sponsored by AMD
Built-in forensics, incident response, and security with Red Hat Enterprise Linux 6
Every security policy provides guidance and requirements for ensuring adequate protection of information and data, as well as high-level technical and administrative security requirements for a system in a given environment. Traditionally, providing security for a system focuses on the confidentiality of the information on it. However, protecting the data integrity and system and data availability is just as important. For example, when processing United States intelligence information, there are three attributes that require protection: confidentiality, integrity, and availability.
Learn more about catching the bad guy in this free white paper.
Sponsored by DLT Solutions
| Designing Electronics with Linux | May 22, 2013 |
| Dynamic DNS—an Object Lesson in Problem Solving | May 21, 2013 |
| Using Salt Stack and Vagrant for Drupal Development | May 20, 2013 |
| Making Linux and Android Get Along (It's Not as Hard as It Sounds) | May 16, 2013 |
| Drupal Is a Framework: Why Everyone Needs to Understand This | May 15, 2013 |
| Home, My Backup Data Center | May 13, 2013 |
- Designing Electronics with Linux
- Making Linux and Android Get Along (It's Not as Hard as It Sounds)
- Dynamic DNS—an Object Lesson in Problem Solving
- Using Salt Stack and Vagrant for Drupal Development
- New Products
- Build a Skype Server for Your Home Phone System
- Validate an E-Mail Address with PHP, the Right Way
- Why Python?
- A Topic for Discussion - Open Source Feature-Richness?
- Tech Tip: Really Simple HTTP Server with Python
- Great
3 hours 30 min ago - Reply to comment | Linux Journal
3 hours 38 min ago - Understanding the Linux Kernel
5 hours 53 min ago - General
8 hours 22 min ago - Kernel Problem
18 hours 25 min ago - BASH script to log IPs on public web server
22 hours 52 min ago - DynDNS
1 day 2 hours ago - Reply to comment | Linux Journal
1 day 3 hours ago - All the articles you talked
1 day 5 hours ago - All the articles you talked
1 day 5 hours ago
Enter to Win an Adafruit Pi Cobbler Breakout Kit for Raspberry Pi

It's Raspberry Pi month at Linux Journal. Each week in May, Adafruit will be giving away a Pi-related prize to a lucky, randomly drawn LJ reader. Winners will be announced weekly.
Fill out the fields below to enter to win this week's prize-- a Pi Cobbler Breakout Kit for Raspberry Pi.
Congratulations to our winners so far:
- 5-8-13, Pi Starter Pack: Jack Davis
- 5-15-13, Pi Model B 512MB RAM: Patrick Dunn
- 5-21-13, Prototyping Pi Plate Kit: Philip Kirby
- Next winner announced on 5-27-13!
Free Webinar: Hadoop
How to Build an Optimal Hadoop Cluster to Store and Maintain Unlimited Amounts of Data Using Microservers
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Some of key questions to be discussed are:
- What is the “typical” Hadoop cluster and what should be installed on the different machine types?
- Why should you consider the typical workload patterns when making your hardware decisions?
- Are all microservers created equal for Hadoop deployments?
- How do I plan for expansion if I require more compute, memory, storage or networking?



Comments
juing oo
Korek Api Gas Fighter Indonesia |
Belanja produk gaya hidup kontemporer dari desainer berbakat dengan harga terjangkau! |
Penting, Panas, Perlu dan Seruu
nice post
Korek Api Gas Fighter Indonesia |
Belanja produk gaya hidup kontemporer dari desainer berbakat dengan harga terjangkau! |
Penting, Panas, Perlu dan Seruu
Computer Monitoring Software Reviews
Extremely useful articles and other content Im highly eager about digesting more. Computer Monitoring Software Reviews
logging
Here's my version of your
loggerbits...if [ $logging ] ; then
logger="/usr/bin/logger"
else
logger="echo >/dev/null"
fi
printDebug is either useful or not, depending on the DEBUG flag (using getopts, I call it "-d").
[ "$DEBUG" -eq 0 ] && printDebug() { echo > /dev/null; } [ "$DEBUG" -eq 1 ] && printDebug() { echo -e "DEBUG: $1" >&2; }cleanup() { rm -rf $tempDir; } printDebug "Defined function 'cleanup'"error() { echo -e "*** FAILED ***\n$1" 1>&2; cleanup; exit 1; } printDebug "Defined function 'error'"Using your suggestions, my trap statement could be
missing a semi colon?
Looks like you got the first one, but there are two other instances where you're missing that annoying final semi-colon. Why does Bash need this?
trap '{ /bin/rm -f $tempfile $temp2; exit 1 }' SIGINT
trap '{ echo ran out of time ; exit 1 }' SIGALRM
I think these should have another semi-colon before the '}'. Without it, you'll get the weird "unexpected end of file" syntax error.
Good article though.
{} not needed
IMHO, { and } are not required with ''trap''.
{} not needed
I agree.
Post new comment