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 ```