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.

+3


source to share


2 answers


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 use preg_match

    (or, if you want to pattern match a few times) preg_match_all

    .
  • Also consider the flag PREG_SET_ORDER

    for preg_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.
+1


source


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.)

+1


source







All Articles