Parsing command arguments in PHP

command-line-arguments, parsing, php

Solution

Regexes are quite powerful: `(?s)(?<!\\)("|')(?:[^\\]|\\.)*?\1|\S+`. So what does this expression mean ?

- `(?s)` : set the `s` modifier to match newlines with a dot `.`

- `(?<!\\)` : negative lookbehind, check if there is no backslash preceding the next token

- `("|')` : match a single or double quote and put it in group 1

- `(?:[^\\]|\\.)*?` : match everything not \, or match \ with the immediately following (escaped) character

- `\1` : match what is matched in the first group

- `|` : or

- `\S+` : match anything except whitespace one or more times.

The idea is to capture a quote and group it to remember if it's a single or a double one. The negative lookbehinds are there to make sure we don't match escaped quotes. `\1` is used to match the second pair of quotes. Finally we use an alternation to match anything that's not a whitespace. This solution is handy and is almost applicable for any language/flavor that supports lookbehinds and backreferences. Of course, this solution expects that the quotes are closed. The results are found in group 0.

Let's implement it in PHP:

$string = <<<INPUT
foo "bar \"baz\"" '\'quux\''
'foo"bar' "baz'boz"
hello "regex

world\""
"escaped escape\\\\"
INPUT;

preg_match_all('#(?<!\\\\)("|\')(?:[^\\\\]|\\\\.)*?\1|\S+#s', $string, $matches);
print_r($matches[0]);

If you wonder why I used 4 backslashes. Then take a look at my previous answer.

Output

Array
(
    [0] => foo
    [1] => "bar \"baz\""
    [2] => '\'quux\''
    [3] => 'foo"bar'
    [4] => "baz'boz"
    [5] => hello
    [6] => "regex

world\""
    [7] => "escaped escape\\"
)

Online regex demo Online php demo

Removing the quotes

Quite simple using named groups and a simple loop:

preg_match_all('#(?<!\\\\)("|\')(?<escaped>(?:[^\\\\]|\\\\.)*?)\1|(?<unescaped>\S+)#s', $string, $matches, PREG_SET_ORDER);

$results = array();
foreach($matches as $array){
   if(!empty($array['escaped'])){
      $results[] = $array['escaped'];
   }else{
      $results[] = $array['unescaped'];
   }
}
print_r($results);

Online php demo

Problem

Is there a native "PHP way" to parse command arguments from a `string`? For example, given the following `string`: ``` foo "bar \"baz\"" '\'quux\'' ``` I'd like to create the following `array`: ``` array(3) { [0] => string(3) "foo" [1] => string(7) "bar "baz"" [2] => string(6) "'quux'" } ``` I've already tried to leverage `token_get_all()`, but PHP's variable interpolation syntax (e.g. `"foo ${bar} baz"`) pretty much rained on my parade. I know full well that I could write my own parser. Command argument syntax is super simplistic, but if there's an existing native way to do it, I'd much prefer that over rolling my own. EDIT: Please note that I am looking to parse the arguments from a `string`, NOT from the shell/command-line. EDIT #2: Below is a more comprehensive example of the expected input -> output for arguments: ``` foo -> foo "foo" -> foo 'foo' -> foo "foo'foo" -> foo'foo 'foo"foo' -> foo"foo "foo\"foo" -> foo"foo 'foo\'foo' -> foo'foo "foo\foo" -> foo\foo "foo\\foo" -> foo\foo "foo foo" -> foo foo 'foo foo' -> foo foo ```

Original source

Related problems