【问题标题】:Getopts in sourced Bash function works interactively, but not in test script?源 Bash 函数中的 Getopts 以交互方式工作,但不能在测试脚本中工作?
【发布时间】:2018-03-22 23:20:05
【问题描述】:

我有一个 Bash 函数 library,其中一个函数在测试中被证明是有问题的。 prunner 是一个函数,旨在提供 GNU Parallel 的一些功能,并避免尝试在 Perl 中使用其他 Bash 函数的范围问题。它支持使用-c 设置针对参数列表运行的命令,以及使用-t 设置同时运行的后台作业数。

在测试中,我最终得到了以下场景:

  • prunner -c "gzip -fk" *.out - 在 test.bash 中按预期工作并以交互方式工作。
  • find . -maxdepth 1 -name "*.out" | prunner -c echo -t 6 - 不起作用,貌似忽略了-c echo

在带有 Bash 4.3 的 Ubuntu 16.04 和带有 Bash 4.4 的 Mac OS X 上进行了测试。

test.bash 中,后者似乎发生的事情是getopts 拒绝处理-c,因此prunner 将尝试在没有给出前缀命令的情况下直接执行参数。奇怪的是我能够观察到它接受-t 选项,所以getopts 至少部分工作。使用 set -x 进行 Bash 调试无法解释为什么会发生这种情况。

这是有问题的函数,稍作修改以使用 echo 而不是 logquit,以便可以与我的库的其余部分分开使用:

    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."
    }

谁能帮我确定为什么-c 选项在通过管道传输到标准输入时无法正常工作prunner

【问题讨论】:

  • printf 替换 echo 的行为如何?
  • 感谢您将 log 替换为 echo 以使您的功能更加独立。进一步考虑这个想法并删除重现问题不需要的所有其他内容怎么样? (这是MCVE中的M)
  • 小提示:变量名不要全部大写。将名称全部大写的变量视为保留供系统使用,因为这是一般约定。如果您虔诚地将变量声明为本地变量,则差异较小,但最好的做法仍然是避免在所有大写字母中使用名称(当然,引用系统环境变量时除外)。
  • 您能否详细说明为什么不使用parallel --embed 将 GNU Parallel 作为函数嵌入到您的 shell 脚本中?

标签: bash shell getopts


【解决方案1】:

我的猜测是你在同一个 shell 中执行这两个命令。在这种情况下,在第二次调用中,OPTIND 的值将是 3(这是它在第一次调用时到达的位置),这就是 getopts 将开始扫描的位置。

如果您使用 getopts 解析函数的参数(而不是脚本),请声明 local OPTIND=1 以避免调用相互干扰。

【讨论】:

  • 就是这样。不确定这些变量是如何设置的,因为我没有在测试脚本中的任何函数之外调用getopts。在调用getopts 之前在函数中本地设置它们可以解决问题。谢谢!
  • @MrDrMcCoy:您所要做的就是调用您的函数两次(或任何其他使用 getopts 的函数)。 OPTIND 是一个 global 变量,包含所有含义(除非声明为本地变量)。这是对 bash 变量作用域的好奇,您可以像这样声明它是本地的; bash 本地人在这方面更像 perl 本地人。
【解决方案2】:

也许您已经这样做了,但请确保将顶级 shell 参数传递给您的函数。该函数将通过调用接收参数,例如:

xyz () {
    echo "First arg: ${1}"
    echo "Second arg: ${2}"
}
xyz "This is" "very simple"

在您的示例中,您应该始终使用标准参数调用函数,以便可以通过 getopts 在方法中处理它们。

prunner "$@"

请注意,pruner 不会修改函数之外的标准参数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-07-02
    • 1970-01-01
    • 1970-01-01
    • 2020-10-15
    • 1970-01-01
    • 2015-07-14
    • 1970-01-01
    • 2020-04-27
    相关资源
    最近更新 更多