Search code examples
bashshellgetopts

Getopts in sourced Bash function works interactively, but not in test script?


I have a Bash function library and one function is proving problematic for testing. prunner is a function that is meant to provide some of the functionality of GNU Parallel, and avoid the scoping issues of trying to use other Bash functions in Perl. It supports setting a command to run against the list of arguments with -c, and setting the number of background jobs to run concurrently with -t.

In testing it, I have ended up with the following scenario:

  • prunner -c "gzip -fk" *.out - works as expected in test.bash and interactively.
  • find . -maxdepth 1 -name "*.out" | prunner -c echo -t 6 - does not work, seemingly ignoring -c echo.

Testing was performed on Ubuntu 16.04 with Bash 4.3 and on Mac OS X with Bash 4.4.

What appears to be happening with the latter in test.bash is that getopts is refusing to process -c, and thus prunner will try to directly execute the argument without the prefix command it was given. The strange part is that I am able to observe it accepting the -t option, so getopts is at least partially working. Bash debugging with set -x has not been able to shed any light on why this is happening for me.

Here is the function in question, lightly modified to use echo instead of log and quit so that it can be used separately from the rest of my library:

    prunner () {
      local PQUEUE=()
      while getopts ":c:t:" OPT ; do
        case ${OPT} in
          c) local PCMD="${OPTARG}" ;;
          t) local THREADS="${OPTARG}" ;;
          :) echo "ERROR: Option '-${OPTARG}' requires an argument." ;;
          *) echo "ERROR: Option '-${OPTARG}' is not defined." ;;
        esac
      done
      shift $(($OPTIND-1))
      for ARG in "$@" ; do
        PQUEUE+=("$ARG")
      done
      if [ ! -t 0 ] ; then
        while read -r LINE ; do
          PQUEUE+=("$LINE")
        done
      fi
      local QCOUNT="${#PQUEUE[@]}"
      local INDEX=0
      echo "Starting parallel execution of $QCOUNT jobs with ${THREADS:-8} threads using command prefix '$PCMD'."
      until [ ${#PQUEUE[@]} == 0 ] ; do
        if [ "$(jobs -rp | wc -l)" -lt "${THREADS:-8}" ] ; then
          echo "Starting command in parallel ($(($INDEX+1))/$QCOUNT): ${PCMD} ${PQUEUE[$INDEX]}"
          eval "${PCMD} ${PQUEUE[$INDEX]}" || true &
          unset PQUEUE[$INDEX]
          ((INDEX++)) || true
        fi
      done
      wait
      echo "Parallel execution finished for $QCOUNT jobs."
    }

Can anyone please help me to determine why -c options are not working correctly for prunner when lines are piped to stdin?


Solution

  • My guess is that you are executing the two commands in the same shell. In that case, in the second invocation, OPTIND will have the value 3 (which is where it got to on the first invocation) and that is where getopts will start scanning.

    If you use getopts to parse arguments to a function (as opposed to a script), declare local OPTIND=1 to avoid invocations from interfering with each other.