Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse string into command and args in JavaScript

I need to parse strings intended for cross-spawn

From the following strings:

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

To an object:

{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'}}

I'm thinking a regex would be possible, but I'd love to avoid writing something custom. Anyone know of anything existing that could do this?

Edit

The question and answers are still valuable, but for my specific use-case I no longer need to do this. I'll use spawn-command instead (more accurately, I'll use spawn-command-with-kill) which doesn't require the command and args to be separate. This will make life much easier for me. Thanks!

like image 750
kentcdodds Avatar asked Sep 03 '16 06:09

kentcdodds


Video Answer


1 Answers

A regular expression could match your command line...

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

... but you wouldn't be able to extract individual words. Instead, you need to match the next word and accumulate it into a command line.

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;
}
like image 96
J Earls Avatar answered Oct 06 '22 00:10

J Earls