开始

今天的主题是如何解析ahk命令行,即如何将一条命令行拆分成开关、脚本路径及脚本参数。

  • 首先,ahk支持的命令行格式如下:
AutoHotkey.exe [Switches] [Script Filename] [Script Parameters]

ahk源码中也有对应描述:

Any special flags (e.g. /force and /restart) must appear prior to the script filespec.
The script filespec (if present) must be the first non-backslash arg.
All args that appear after the filespec are considered to be parameters for the script
and will be stored in A_Args

  • 然后,对于开关部分,有些无值,有些是有值的,比如/include,ahk是这样做的:
    image

实际上,这些开关我常用的只有/force/restart,所以在我的代码中没有特殊处理这些开关。

  • 再是脚本名,它有些特殊处;比如可以是*号、可以省略,具体在文档中有介绍。

  • 最后的脚本参数只有一个要注意的点,就是空格用引号包围。

代码

我的思路是这样的,逐行逐字符解析,当:

  • 遇到转义符时,判断后面字符是否为引号
    • 如果是,则记录引号
    • 否则,记录转义符(意味着转义符无需再次转义)
  • 遇到引号时
    • 如果已经在引号内,记录,并切换引号状态(是否在引号内)
    • 否则仅切换引号状态
  • 遇到空格且不在引号内,说明读取完了部分参数,记录参数
  • 其他情况记录当前字符

变成代码如下:

ParseCmdLine(cmdLine) {
  if !cmdLine
    throw Error('invalid command line:blank input')
  switchs := [], args := [], _s := '', _q := false, i := 1
  while i <= cmdLine.length {
    if (_c := cmdLine.charAt(i)) = '\' && i < cmdLine.length && cmdLine.charAt(i + 1) = '"' {
      _s .= '"', i++
    } else if _c = '"' {
      if _q
        _s[1] = '/' ? switchs.push(_s) : args.push(_s), _s := ''
      _q := !_q
    } else if _c = ' ' && !_q {
      if _s.length > 0
        _s[1] = '/' ? switchs.push(_s) : args.push(_s), _s := ''
    } else _s .= _c
    i++
  }
  if _s.length > 0
    args.push(_s)
  if args.Length < 2
    throw Error('invalid command line:bad command line')
  return { ahkExePath: args.shift(), switchs: switchs, filespec: args.shift(), params: args }
}

在命名方面有些抽象,但是我写ahk总是这样。

应用

上面的解析代码不只可用于解析ahk命令行,我将它用在了MeowTool的命令解析上。只是命令形式是下面这样:

cmd [-param] [-key=val] [target] [extra]

解析代码如下:

Parse(cmd) {
  try ReplaceAlias(&cmd)
  catch as e
    return _fail(e.Message)
  _esc := '\', _qc := "'"
  args := [], switchs := [], p := [], kp := {}, _s := '', i := 1, _q := false
  while i <= cmd.Length {
    if (_c := cmd.charAt(i)) = _esc && i < cmd.length && cmd.charAt(i + 1) = _qc {
      _s .= _qc, i++
    } else if _c = _qc {
      if _q
        _push(_s), _s := ''
      _q := !_q
    } else if _c = A_Space && !_q {
      if _s.length > 0
        _push(_s)
      _s := ''
    } else _s .= _c
    i++
  }
  if _s.length > 0
    args.push(_s)
  return _succ(args.shift(), {
    params: p,
    kvparams: kp,
    target: args.Length ? args.shift() : '',
    extra: args,
    raw: cmd
  })

  _push(s) {
    if s[1] != '-'
      args.push(s)
    if s.Length = 1
      return
    if _ := InStr(s, '=')
      kp[s.substring(2, _)] := s.substring(_ + 1)
    else p.Push(_s.substring(2))
  }
  _succ(w, o) => { valid: true, which: w, parsed: o }
  _fail(msg) => { valid: false, msg: msg }
}

可以看到关键部分是一致的。

posted on 2024-07-30 00:45  落寞的雪  阅读(45)  评论(0编辑  收藏  举报