如何使用正则表达式解析命令行?

11
我想将类似命令行的字符串拆分为单个字符串参数。它所需的正则表达式如何书写?问题在于参数可能会被引号引起来。例如:
"param 1" param2 "param 3"
应该拆分为: param 1, param2, param 3
16个回答

20

你不应该使用正则表达式来处理这个问题。相反,编写一个解析器或使用语言提供的解析器。

我不明白为什么会因此被踩。在Python中可以这样实现:

>>> import shlex
>>> shlex.split('"param 1" param2 "param 3"')
['param 1', 'param2', 'param 3']
>>> shlex.split('"param 1" param2 "param 3')
Traceback (most recent call last):
    [...]
ValueError: No closing quotation
>>> shlex.split('"param 1" param2 "param 3\\""')
['param 1', 'param2', 'param 3"']

现在告诉我,为了解决这个问题而费尽心思地构建正则表达式是否值得麻烦。


1
我同意。这将是一个更好的解决方案,特别是如果您需要在字符串中放置引号:"param""1" param2... - rslite
+1 - 像解析XML一样,这对于正则表达式来说并不是一个好的问题。 - slim
8
完全胡说八道。这对于正则表达式来说是一个简单的问题,它与解析XML没有任何共同之处。 - Account deleted
"shelx" 必须是答案,同意 hop!太对了!" - shahjapan
1
在我的情况下,这很值得,因为:我需要在一些psql脚本中使用它,而另一种选择就会成为与plpgsql的麻烦。 - philipp

7

7
("[^"]+"|[^\s"]+)

我使用C++

#include <iostream>
#include <iterator>
#include <string>
#include <regex>

void foo()
{
    std::string strArg = " \"par   1\"  par2 par3 \"par 4\""; 

    std::regex word_regex( "(\"[^\"]+\"|[^\\s\"]+)" );
    auto words_begin = 
        std::sregex_iterator(strArg.begin(), strArg.end(), word_regex);
    auto words_end = std::sregex_iterator();
    for (std::sregex_iterator i = words_begin; i != words_end; ++i)
    {
        std::smatch match = *i;
        std::string match_str = match.str();
        std::cout << match_str << '\n';
    }
}

输出:

"par   1"
par2
par3
"par 4"

6

不考虑实现语言,您的正则表达式可能如下所示:

("[^"]*"|[^"]+)(\s+|$)

第一部分"[^"]*"查找不包含嵌入引号的引用字符串,第二部分[^"]+查找非引号字符序列。 \s+匹配一个分隔空格序列,$匹配字符串的结尾。


1
你的正则表达式失败案例: <" " param2 "" bozo "ninny" "param 3"> 注意:1) 答案中保留了引号 2) 在 bozo 后包含尾随空格。可能还有其他错误。 - Account deleted
抱歉,这些注释会删除空格 - 在“bozo”后面应该有很多空格。 - Account deleted

4

正则表达式:/[\/-]?((\w+)(?:[=:]("[^"]+"|[^\s"]+))?)(?:\s+|$)/g

示例:/P1="长字符串" /P2=3 /P3=short PwithoutSwitch1=any PwithoutSwitch2

这个正则表达式可以解析按以下规则构建的参数列表:

  • 参数由一个或多个空格分隔。
  • 参数可以包含开关符号(/-)。
  • 参数由名称和值组成,两者之间用等于号 (=) 或冒号 (:) 分隔。
  • 名称可以是字母数字和下划线的集合。
  • 值可以不存在。
  • 如果值存在,则可以是任何符号的集合,但如果它有空格,则值应该被引用。

这个正则表达式有三个组:

  • 第一组包含没有开关符号的全部参数,
  • 第二组仅包含名称,
  • 第三组仅包含值(如果存在)。

对于上面的示例:

  1. 完整匹配:/P1="长字符串"
    • 第1组:P1="长字符串"
    • 第2组:P1
    • 第3组:"长字符串"
  2. 完整匹配:/P2=3
    • 第1组:P2=3
    • 第2组:P2
    • 第3组:3
  3. 完整匹配:/P3=short
    • 第1组:P3=short
    • 第2组:P3
    • 第3组:short
  4. 完整匹配:PwithoutSwitch1=any
    • 第1组:PwithoutSwitch1=any
    • 第2组:PwithoutSwitch1
    • 第3组:any
  5. 完整匹配:PwithoutSwitch2
    • 第1组:PwithoutSwitch2
    • 第2组:PwithoutSwitch2
    • 第3组:不存在。

2
大多数编程语言都有其他函数(无论是内置的还是由标准库提供的),它们可以比构建自己的正则表达式更轻松地解析命令行,并且您知道它们会准确地完成任务。如果您编辑帖子以确定您使用的语言,我相信这里的某个人一定能指出该语言中使用的函数。
正则表达式是非常强大的工具,对于各种事情都很有用,但也有许多问题不适合使用它们。这就是其中之一。

2

这将从参数中分离出一个exe;从exe中去除括号;假设数据干净:

^(?:"([^"]+(?="))|([^\s]+))["]{0,1} +(.+)$

每次你将会有两个匹配,分别是三个匹配组:

  1. 如果被括号包裹,那么就是该exe
  2. 如果未被括号包裹,那么就是该exe
  3. 参数的组合

示例:

"C:\WINDOWS\system32\cmd.exe" /c echo this

匹配 1:C:\WINDOWS\system32\cmd.exe

匹配 2:$null

匹配 3:/c echo this

C:\WINDOWS\system32\cmd.exe /c echo this

匹配 1:$null

匹配 2:C:\WINDOWS\system32\cmd.exe

匹配 3:/c echo this

"C:\Program Files\foo\bar.exe" /run

匹配1:C:\Program Files\foo\bar.exe

匹配2:$null

匹配3:/run

想法:

我��信你需要创建一个循环来捕获可能无限数量的参数。

这个正则表达式可以轻松地循环到第三个匹配,直到匹配失败;没有更多的参数了。


1

既然有 Python 的答案,那我们也应该有 Ruby 的答案 :)

require 'shellwords'
Shellwords.shellsplit '"param 1" param2 "param 3"'
#=> ["param 1", "param2", "param 3"] or :
'"param 1" param2 "param 3"'.shellsplit

1
如果你只是担心引号的问题,那么可以编写一个简单的循环,逐个字符地将其转储到字符串中,忽略引号。
或者,如果你正在使用一些字符串操作库,可以使用它来删除所有引号,然后将它们连接起来。

1

虽然答案不是正则表达式特定的,但回答了Python命令行参数解析

  • 短横线和双短横线标志(dash and double dash flags)
  • 基于SO答案的int/float转换

import sys

def parse_cmd_args():
  _sys_args = sys.argv
  _parts = {}
  _key = "script"
  _parts[_key] = [_sys_args.pop(0)]

  for _part in _sys_args:
    # Parse numeric values float and integers
    if _part.replace("-", "1", 1).replace(".", "1").replace(",", "").isdigit():
      _part = int(_part) if '.' not in _part and float(_part)/int(_part) == 1 else float(_part)
      _parts[_key].append(_part)
    elif "=" in _part:
      _part = _part.split("=")
      _parts[_part[0].strip("-")] = _part[1].strip().split(",")
    elif _part.startswith(("-")):
      _key = _part.strip("-")
      _parts[_key] = []
    else:
      _parts[_key].extend(_part.split(","))

  return _parts


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接