带有 shell 脚本的上下文无关语法解析器

带有 shell 脚本的上下文无关语法解析器

前几天,我为一个非技术团队编写了一个脚本,我想我应该用一个读起来更像句子的命令来替换神秘的命令行标志。

我想出了一个命令行:
<script.sh> run tests for <module> ... in <language> ...

关键字“for”引入模块列表,而“in”引入语言列表。 (顺便说一句,“in”本身将来可能会成为一种语言代码。)

我解析它的方法是将其高级结构定义为 .如果 + =“运行测试”,我会启动特定的解析来查找“for”和“in”关键字。

接下来,我想将语法更改为:
<script.sh> run <module> ... tests in <language> ...

在我看来,这读起来更好。

我有能力通过手工制作的 Bash 参数解析来完成这项工作,但在我看来,这太复杂了。另一种选择是 Lex/Yacc:脚本可以编写临时 Lex+Yacc 脚本并使用它们来解析其命令行。这似乎也太过分了。

有没有一种好的、直接的方法来定义一个简单的上下文无关语法,可以用来将上面的命令行解析为变量?我很高兴听到起点而不是成熟的解决方案。

答案1

它相当笨重,但它应该为您指明正确的方向。

解析只是解释(或编译和执行)指定程序过程的一个阶段。使用 shell 参数作为词法标记很方便。就解释而言,我强烈建议将解析和解释器逻辑与工作的实现分开——使用明确命名的函数来完成工作。甚至可以使用无操作门控进行编写,这样您就可以跟踪和调试,而无需做更多工作。bash -x是你的朋友,bash -x ./script.bash会让你看到执行的痕迹。

我鼓励您从这样的解析逻辑中调用命名函数。编写或修改很挑剔,并且当您可以交换已调用函数 echo 名称而不是执行的函数时,会更容易。

#!/usr/bin/env bash

: ${DEBUG:=}; shopt -s extglob; [[ $DEBUG ]] && shopt -p extglob

acceptArg () {
  local spec="$1" cb="$2" arg="$3" status=([args]=0 [matched]=0); shift 3;

  # https://unix.stackexchange.com/a/234415/61350  # how-can-i-use-a-variable-as-a-case-condition
  matcher="@($spec)"
  case "$arg" in
    $matcher) status[matched]=1; $cb ;;
    *) [[ $DEBUG ]] && echo "spec: '$spec' failed to match arg: '$arg'";;
  esac
  [[ $DEBUG ]] && declare -p status
  return $((status[matched] == 1))
}

count=0
incr(){ echo $((++count)); }
handleFirst(){ incr; }
handleSecond(){ incr; }

acceptArg "f|first" handleFirst "$1";
acceptArg "s|second|*2" handleSecond "$2";
[[ $? == 1 ]] && echo done || echo failed

我对同样的事情很感兴趣,几个月或几年以来我一直想写点东西。感谢您使问题陈述具体化。我正在处理更新https://gist.github.com/mcint/8a589500c44d4dc08dcb09b80882c2fd

相关内容