Parsing a string to output an array from the command line
I am working on a new Symfony 2 project that will manage a panel for Docker containers.
In this project, I am executing some commands using a exec()
PHP function .
I am trying to parse the output of the following command:
docker create tutum/lamp:latest --name test 2>&1
When the command is successful, I get the container id on the line, which is nice and easy to use, but when the problem occurs, it is not the same. The result is a string with the syntax var = "data", which I want to parse to get an array.
Command output:
time="2015-06-21T11:33:26+02:00" level="fatal" msg="Error response from daemon: Conflict. The name \"test\" is already in use by container XXXXXXXX. You have to delete (or rename) that container to be able to reuse that name."
I want to have something like this:
Array( time => "2015-06-21T11:33:26+02:00", level => "fatal" ...);
I know that I have to do regex parsing. After a while (regex and I'm not very best friends) I get this regex (tested at https://regex101.com/ ):
/([a-zA-Z]+)="((.*)*)"/
I used preg_split function, I'm not sure if it's a good one.
preg_split('/([a-zA-Z]+)="((.*)*)"/', $output)
Result:
array(2) { [0]=> string(0) "" [1]=> string(0) "" }
Do you have any suggestions to help me? Many thanks for your help.
source to share
TL; DR: This should work:
preg_match_all(',([a-z]+)="((?:[^"]|\\\\")*[^\\\\])",', $a, $matches, PREG_SET_ORDER);
var_dump($matches);
The latter var_dump
prints the following data structure, which should be easily handled:
array(3) {
[0] => array(3) {
[0] => string(32) "time="2015-06-21T11:33:26+02:00""
[1] => string(4) "time"
[2] => string(25) "2015-06-21T11:33:26+02:00"
}
[1] => array(3) {
[0] => string(13) "level="fatal""
[1] => string(5) "level"
[2] => string(5) "fatal"
}
[2] => array(3) {
[0] => string(179) "msg="Error response from daemon: Conflict. The name \\"test\\" is already in use by container XXXXXXXX. You have to delete (or rename) that container to be able to reuse that name.""
[1] => string(3) "msg"
[2] => string(173) "Error response from daemon: Conflict. The name \\"test\\" is already in use by container XXXXXXXX. You have to delete (or rename) that container to be able to reuse that name."
}
}
Why it works
Explanation of regex:
([a-z]+) # Match the label ("time", "level" or "msg")
= # Self-explanatory
"((?:[^"]|\\\\")*[^\\\\])" # This is the tricky part:
# Match the quoted string; this is a sequence
# of (a) non-quote characters ([^"]) or
# (b) escaped quote characters (\\\\").
Some other notes:
-
preg_split
uses a regex to match the token where the string should be split. This is not what you want in this case; you want to return the parts of the string that were matched against the regex. To do this, you should usepreg_match
(or, if you want to pattern match a few times)preg_match_all
. - Also consider the flag
PREG_SET_ORDER
forpreg_match_all
. This flag causes the result to$matches
contain one line for each label from the output message, making it easier to handle the data structure. Try it and see what happens if you don't leave.
source to share
This is because of the greedy point that gobbles up your line to the last "
. Make it lazy, do it like this:
if(preg_match_all('/(\w+)="(.*?)(?<!\\\)"/s', $str, $out))
print_r(array_combine($out[1], $out[2]));
\w
is short for [a-zA-Z0-9_]
. Lookbehind (?<!\\\)
to eat the runaway quotes ( see Regex101 ).
A flag is used s
to match point to newline point. Check for eval.in , output to:
Array ([time] => 2015-06-21T11: 33: 26 + 02: 00 [level] => fatal [msg] => Error response from daemon: conflict. The name \ "test \" is already in use by the XXXXXXX container. You must delete (or rename) this container to be able to reuse this name.)
source to share