0
\$\begingroup\$

When I test shell programs for portability, I often want to run them with different implementations of awk, sed, and several other tools. Of course I could make these shell programs flexible by introducing command variables like : ${AWK:-/usr/bin/awk}, but that would encode the absolute path of the command, which differs between the operating systems.

In order to keep the shell programs as clean as possible, I instead wrote a small tool that I called pathenv. It is very similar to the well-known env. It is run like this:

pathenv awk=/usr/xpg4/bin/awk sh=/bin/ksh my-command args...

This lets the shell programs assume that the PATH is set up properly and that the utilities can be run by their plain names.

The idea of this utility is quite simple, therefore I wonder that I invented it as late as 50 years after the other UNIX commands. A possible reason is that it only applies in very few situations since on a single system, the commands all have well-known paths and PATH is set up properly. This utility is probably most useful when a shell program can be run in very different environments, like the typical GNU configure scripts.

Here's the implementation:

#! /bin/sh
set -eu

ln="/bin/ln"
mktemp="/usr/bin/mktemp"
rm="/bin/rm"

usage() {
  printf 'usage: %s <name>=<path>... <command> <arg>...\n' "$0"
  printf '%s\n' \
    '' \
    'Runs <command> <arg>... in an environment where the' \
    'commands given by <name>s are redirected to the given paths'
  exit 1
}

tmpdir="$("$mktemp" -d)"
trap '"$rm" -rf "$tmpdir"' EXIT

while [ $# -gt 0 ]; do
  case "$1" in -*) usage 1>&2;; esac

  cmd_name="${1%%=*}"
  cmd_path="${1#*=}"

  [ "x$cmd_name" != "x$1" ] || break

  case "$cmd_path" in "" | [!/]*)
    printf '%s: command path "%s" for "%s" must be absolute\n' \
      "$0" "$cmd_path" "$cmd_name" 1>&2
     exit 1
  esac

  [ -x "$cmd_path" ] && [ -f "$cmd_path" ] || {
    printf '%s: command path "%s" for "%s" must be executable\n' \
      "$0" "$cmd_path" "$cmd_name" 1>&2
    exit 1
  }

  "$ln" -s "$cmd_path" "$tmpdir/$cmd_name"
  shift
done

PATH="$tmpdir:$PATH"
export PATH

exec "$@"
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.