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