My new .bash_profile

Ovid on 2007-03-05T14:21:37

When I ssh into our development server, I automatically CD into a work directory, do a 'cvs up -d' (-d picks up new directories) and then run all of the tests. However, I almost always ssh into dev twice because one window is for my code and another window is for my tests and I'm tired of hitting <ctrl-c> all the time on the second window. With the help of smylers explaining some odd (to me) bits of bash syntax, I now use something like this:

#!/usr/local/bin/bash

if [ -f ~/.bashrc ]; then source ~/.bashrc; fi

cd /home/cpoe/work/main/
timestamp='.login_time'

if [ -e $timestamp ]; then
    last_time=`cat $timestamp`
else
    last_time=0
fi

curr_time=`date +%s`
let elapsed="$curr_time - $last_time"
if [ $elapsed -gt 600 ]   # five minutes
then
    `perl -e 'print time' > $timestamp`
    /usr/bin/cvs up -d
    /usr/local/bin/runtests -lr
fi

With that, if I've ssh'd into the dev server and run the tests within the last five minutes, the next ssh will not do the cvs up or run the tests. It could probably be cleaned up more since I'm not much of a bash programmer, but it's pretty handy.


Various improvements

Aristotle on 2007-03-06T03:37:53

Unlike sh, bash has arithmetic evaluation that would make your script much simpler. I also wouldn’t check for the dotfile’s existence, just read it with error messages suppressed. It’s also bizarre that you’re using date +%s to get the timestamp in one place and perl -e 'print time' in another – and the backticks around that second place are even more so. All in all here’s how I’d write that:

#!/usr/local/bin/bash

[ -r ~/.bashrc ] && source ~/.bashrc

timestamp=.login_time

cd /home/cpoe/work/main/

last_time=$( cat "$timestamp" 2>&- )
curr_time=$( date +%s )

if (( curr_time - last_time > 600 ))  # five (???) minutes
then
    echo "$curr_time" > "$timestamp"
    /usr/bin/cvs up -d
    /usr/local/bin/runtests -lr
fi

But actually, “run the tests if it’s been 5 minutes since my last login” strikes me as a hack. I think what you rather want to say is “skip the tests if cvs up didn’t find any updates to pull.” That would look something like this:

#!/usr/local/bin/bash

[ -r ~/.bashrc ] && source ~/.bashrc

timestamp=.login_time

cd /home/cpoe/work/main/

tmp=$( mktemp ) && {
    /usr/bin/cvs up -d | tee "$tmp"
    [ -s "$tmp" ] && /usr/local/bin/runtests -lr
    rm -f "$tmp"
}

This is based on the fact that cvs prints its U foo/bar.c lines to stdin, but its chatter to stderr. So it copies cvs’s stdout to a temporary file whose size will be nonzero only if cvs actually did any updates. The tests run only if that’s the case.

Re:Various improvements

Ovid on 2007-03-26T10:47:58

By the way, I did implement many of your suggestions, thank you. However, I deliberately didn't want to go with the 'don't run tests if there are no updates to CVS'. There are plenty of other ways -- admittedly less common -- that tests can fail even without CVS updates. When those failures occur, I'd like to catch them!

Re:Various improvements

Aristotle on 2007-03-26T11:45:06

Ah. Btw, a much more direct way to do this just occured to me: use the file’s mtime.

#!/usr/local/bin/bash

[ -r ~/.bashrc ] && source ~/.bashrc

timestamp=.login_time

cd /home/cpoe/work/main/

if [[ `find "$timestamp" -mmin +5 -print` ]] ; then
    touch "$timestamp"
    /usr/bin/cvs up -d
    /usr/local/bin/runtests -lr
fi

The idiomatic bit here is using find to check the file’s age. The -mmin +5 predicate means “if the mtime of the file under consideration is equal to or greater than 5 minutes.” Since file is given only a single filename to check, its output will be empty if that one file was last touched less than 5 minutes ago. The condition is then false, so nothing happens.