JavaScript中将字符串解析为命令和参数

10

我需要解析用于cross-spawn的字符串。

从以下字符串中:

cmd foo bar
cmd "foo bar" --baz boom
cmd "baz \"boo\" bam"
cmd "foo 'bar bud' jim" jam
FOO=bar cmd baz

对于一个对象:

{command: 'cmd', args: ['foo', 'bar']}
{command: 'cmd', args: ['foo bar', '--baz', 'boom']}
{command: 'cmd', args: ['baz "boo" bam']}
{command: 'cmd', args: ['foo \'bar bud\' jim', 'jam']}
{command: 'cmd', args: ['baz'], env: {FOO: 'bar'}}

我认为可以用正则表达式实现,但我希望避免编写自定义代码。是否有现成的工具可供使用?

编辑

这个问题和答案仍然有价值,但针对我的特定用例,我不再需要这样做。我将使用spawn-command(更准确地说,我将使用spawn-command-with-kill),它不需要将commandargs分开。这将让我的生活变得更轻松。谢谢!

3个回答

4

你可以使用正则表达式自己编写解析参数的代码,但我强烈建议使用以下两种方式之一:

这两种工具都经过了实战验证和广泛支持。minimist每个月大约有3000万次下载量,而yargs的下载量几乎半数。

很可能你可以通过其中任意一种找到想要的CLI语法,但需要注意的是env支持应该单独处理(我无法想象为什么你希望命令行本身能够影响环境变量的设置)。


yargs 不会处理配置文件格式(FOO=bar cmd baz),对吧? - Soviut
在Node中,“FOO=bar”根本不会出现在“process.argv”中,因此yargs和minimist都会忽略它。我强烈建议使用“process.env”而不是尝试从命令解析环境变量,因为有许多设置方式,除非有非常特定的原因这样做,否则将支持限制为在命令开始时设置的变量将是意外的。 - Jed Watson
2
所以,更明确地说,我正在开发 p-s,它允许您指定命令字符串,并将其作为子进程调用。使用 cross-spawn,您需要分别提供 commandargs。但是,如果我使用 spawn-command,那么我就不必这样做(我可以直接发送整个命令),这消除了我一开始需要这样做的需求! - kentcdodds

2

你可以使用原始的正则表达式,但你正在构建的称为分词器。 你想要分词器的原因是处理某些上下文,例如包含空格的字符串,你不想在它们上面进行拆分。

有现成的通用库专门用于解析和标记化,可以处理字符串、块等情况。

https://www.npmjs.com/package/js-parse

此外,大多数命令行格式和配置文件格式已经具有解析器/分词器。 你可能想利用这些,并将每个结果规范化为你的对象结构。


2

正则表达式可以匹配您的命令行...

^\s*(?:((?:(?:"(?:\\.|[^"])*")|(?:'[^']*')|(?:\\.)|\S)+)\s*)$

...但您将无法提取单个单词。相反,您需要匹配下一个单词并将其累加到命令行中。

function parse_cmdline(cmdline) {
    var re_next_arg = /^\s*((?:(?:"(?:\\.|[^"])*")|(?:'[^']*')|\\.|\S)+)\s*(.*)$/;
    var next_arg = ['', '', cmdline];
    var args = [];
    while (next_arg = re_next_arg.exec(next_arg[2])) {
        var quoted_arg = next_arg[1];
        var unquoted_arg = "";
        while (quoted_arg.length > 0) {
            if (/^"/.test(quoted_arg)) {
                var quoted_part = /^"((?:\\.|[^"])*)"(.*)$/.exec(quoted_arg);
                unquoted_arg += quoted_part[1].replace(/\\(.)/g, "$1");
                quoted_arg = quoted_part[2];
            } else if (/^'/.test(quoted_arg)) {
                var quoted_part = /^'([^']*)'(.*)$/.exec(quoted_arg);
                unquoted_arg += quoted_part[1];
                quoted_arg = quoted_part[2];
            } else if (/^\\/.test(quoted_arg)) {
                unquoted_arg += quoted_arg[1];
                quoted_arg = quoted_arg.substring(2);
            } else {
                unquoted_arg += quoted_arg[0];
                quoted_arg = quoted_arg.substring(1);
            }
        }
        args[args.length] = unquoted_arg;
    }
    return args;
}

2
哇,谢谢!说实话,我现在觉得我不需要这个了,但是你的正则表达式技能让我惊叹! - kentcdodds

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