next::method in bash scripts

In Pre-commit hooks and breaking the build, I wrote a little wrapper around 'svn' to ensure that I could locally alter how it functions. However, I hard-coded the name to the actual executable. I should actually be doing the equivalent of $self->next::method to find the next executable in my path. So I wrote the following bash function to give this a try.

function next() {
  curr_exec=$1
  shift
  if [ -z $curr_exec ]; then
    echo "next_exec()" requires an argument
    exit 1
  fi
  if ! type $curr_exec >/dev/null 2>&1; then
    echo "next_exec()" argument must be an executable
    exit 2
  fi
  curr_path=$(echo $curr_exec | sed 's|/\w*$||')
  executable=$(basename $curr_exec)

  found=0
  for path in `echo $PATH | sed  's/:/\n/g'`; do
    if [ "$path" = "$curr_path" ]; then
      found=1
    elif [ $found -eq 1 ]; then
      if [ -x "${path}/$executable" ]; then
        next_exec="${path}/$executable"
        break
      fi
    fi
  done

  if [ -z $next_exec ]; then
    echo Could not find a next_exec for $curr_exec
    exit 3
  else
    $next_exec "$@"
  fi
}

This needs some work, but basically, it figures out your path and your executable name. Then it searches through the path to find the next executable with that name. This, I think, makes it safer to write override scripts for various executables and redispatch to them.

So with my svn wrapper, I can call my wrapper or call the thing it wraps.

svn diff
next svn diff

next seems like an awfully common word, though. Will this break anything?

3 Comments

A simpler solution is to write your wrapper as a function, and use 'command svn ARGS' to run svn. 'command' ignores shell functions when searching for the program to run. I use it to wrap rdesktop:

rdesktop () {
    command rdesktop -g 90% "$@"
}

This approach is limited: it's probably bash-specific, and you can't put the function into a stand-alone wrapper unless you change the name - if your stand-alone wrapper is named svn, then 'command svn' will find it first in $PATH.

You’re trying to write sh like you’d write Perl or some other language at that level. That does not make for very idiomatic sh. Also, you’re sticking to generic primitives instead of using the sh “library”, akin to a Perl programmer who doesn’t use any modules.

I’d write it something like this:

function next() {
    case $1 in
        */*) ;;
        '')  echo 'next requires an argument'            ; exit 1 ;;
        *)   echo 'next must be passed an absolute path' ; exit 2 ;;
    esac

    skip=1
    which -a $(basename "$1") \
    | nl | sort -uk2 | sort -nk1,1 | while read linenum cmd
    do
        case "$cmd" in
            "$1") skip= ; continue ;;
            *) [ "$skip" ] && continue
        esac

        shift
        "$cmd" "$@"
        return $?
    done

    echo "Could not find next for $1" ; exit 3
}

And it would be called as

next $0 diff

Leave a comment

About Ovid

user-pic Have Perl; Will Travel. Freelance Perl/Testing/Agile consultant. Photo by http://www.circle23.com/. Warning: that site is not safe for work. The photographer is a good friend of mine, though, and it's appropriate to credit his work.