bash: timing long-running processes
February 3, 2009 1:44 pmThe innertubes have spoken. According to the search terms I’m seeing in my access logs, you interwebbernauts want to see more bash articles. Very well, then.
Every now and then I write a bash script that might take an hour or more to run. It’s nice to know exactly how long it took, so you can plan accordingly when you want to run it the next time. So I formalized some bash timing concepts I’ve been using for years and packaged them up into a code library you can use in your own scripts.
If you’re just interested in the code, you can skip to the bottom of this entry and cut-and-paste the parts you want into locations of your choosing. If you’ve got a minute, I’ll explain some of the non-obvious bits.
bash functions can only return small integers, typically not larger than 255 or so. They are intended for use as values you’d return to the shell, when the script is finished running. If you want to write a function that returns a string or a large integer, you’re out of luck. The way I get around that one is to write functions that use echo to print a string to stdout. The output can be captured by a calling function using this rather awkward syntax:
returnValue=$(functionName)
Unfortunately, variables in bash scripts are by default global, and therefore available to all functions. I don’t like that very much, so my local variable declarations are all prefixed with the keyword local, which is supposed to make them visible only within the current function. I have a sneaking suspicion that supposedly local variables can still “leak” into other functions, however. I suppose I should investigate that situation and use it as the basis for a future blog post.
Finally, you can use the source keyword to pull functions from one script into another, like this:
source timerlib.sh
Now, on to the code. First up is timerlibtest.sh, a script that demonstrates use of the timerlib functions.
#!/bin/sh # timerlibtest.sh -- test for timerlib.sh # by allen brunson allen.brunson@gmail.com february 3 2009 # pull in timerlib functions source timerlib.sh # this function shows how to time a long-running process function main() { # save current time at the beginning local time=$(timerStart) # put whatever you want to time here echo "starting long-running process ..." sleep 2 # display how long it took timerStop $time "elapsed time:" } # execution start main $*
Here is the text for timerlib.sh:
#!/bin/sh # timerlib.sh -- functions for timing long-running operations # by allen brunson allen.brunson@gmail.com february 3 2009 # return seconds since 1970 # does not work on some unix variants. check 'man date' for details function timerCurrent() { date "+%s" } # inputs a number of seconds, outputs a string like "2 minutes, 1 second" # $1: number of seconds function timerLengthString() { local days=$((0)) local hour=$((0)) local mins=$((0)) local secs=$1 local text="" # convert seconds to days, hours, etc days=$((secs / 86400)) secs=$((secs % 86400)) hour=$((secs / 3600)) secs=$((secs % 3600)) mins=$((secs / 60)) secs=$((secs % 60)) # build full string from unit strings text="$text$(timerLengthStringPart $days "day")" text="$text$(timerLengthStringPart $hour "hour")" text="$text$(timerLengthStringPart $mins "minute")" text="$text$(timerLengthStringPart $secs "second")" # trim leading and trailing whitespace text=${text## } text=${text%% } # special case for zero seconds if [ "$text" == "" ]; then text="0 seconds" fi # echo output for the caller echo ${text} } # formats a time unit into a string # $1: integer count of units: 0, 6, etc # $2: unit name: "hour", "minute", etc function timerLengthStringPart() { local unit=$1 local name=$2 if [ $unit -ge 2 ]; then echo " ${unit} ${name}s" elif [ $unit -ge 1 ]; then echo " ${unit} ${name}" else echo "" fi } # useful for testing timerLengthString function timerLengthStringTest() { local days=$((86400)) local hour=$((3600)) local mins=$((60)) local secs=$((1)) timerLengthString 0 timerLengthString 20 timerLengthString $(( ($hour * 3) + ($mins * 1) + ($secs * 52) )) timerLengthString $(( ($days * 1) + ($secs * 14) )) } # synonym for timerCurrent function timerStart() { timerCurrent $* } # display final elapsed time # $1: value returned from an earlier call to timerStart() # $2: optional descriptive string, such as "total wait time:" function timerStop() { local desc=$2 local secs=$((0)) local stop=$(timerCurrent) local text="" local time=$1 if [ "$desc" != "" ]; then text="$desc " fi secs=$(( $stop - $time )) text="$text$(timerLengthString $secs)" echo $text }
Categories: bash, programming
Comments Off


No Responses to “bash: timing long-running processes”