2
\$\begingroup\$

This is my idea on how to turn Shell exec output in PHP to array output. I just want to share it and get feedback in case there is any better way.

Below is a code example to get server info from GCE.

$shell_command  = "gcloud compute instances list";
$result = shell_exec($shell_command);
//echo "\n$result\n";
$data = explode("\n",$result);
//print_r($data);
$headers = turn_linux_row_to_array($data[0]);
unset($data[0]);
$servers_array = array();
foreach($data as $row){
  $server_data = turn_linux_row_to_array($row,$headers);
   if(!empty($server_data)){
     $servers_array[] = $server_data;
   }
}
//function to move text out put to array
function turn_linux_row_to_array($row,$headers=array()){
$final_data_array = array();
$data_array_position = 0;
$row_length = strlen($row);
$current_word = '';
//echo "\nRow length is :: $row_length\n";
for($i=0; $i<$row_length; $i++){
   $char = substr( $row, $i, 1 );
    if($char != ' '){
     //This is a part of a value
     $current_word .=$char;
    }else{
    if($current_word!=''){
       if(!empty($headers) && isset($headers[$data_array_position]) ){
          $final_data_array[$headers[$data_array_position]] = 
    $current_word;
       }else{
         $final_data_array[] = $current_word;
       }
       $data_array_position++;
   }
   $current_word = '';
 }
}
//print_r($final_data_array);
return $final_data_array;
}
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

This code is complicated, and likely buggy.

  • If a line does not end in a space character, then the last word on that line will be ignored.
  • The output of gcloud compute instances list is a table. For example:

    NAME                                             ZONE          MACHINE_TYPE   PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP     STATUS
    gke-cluster-asia-eas-default-pool-dc8f484c-knbs  asia-east1-a  n1-standard-1               10.140.0.2   104.155.227.25  RUNNING
    gke-cluster-asia-eas-default-pool-dc8f484c-x8cv  asia-east1-a  n1-standard-1               10.140.0.3   104.199.226.16  RUNNING
    gke-cluster-asia-eas-default-pool-dc8f484c-z5wv  asia-east1-a  n1-standard-1               10.140.0.4   104.199.134.9   RUNNING
    

    If the table contains an empty value (such as for the "PREEMPTIBLE" column above), then the turn_linux_row_to_array() function will ignore it, resulting in a mismatch between the headers and the values.

I don't recommend trying to improve this code. Instead, noting that all Google Cloud commands accept a --format option, you should choose a format that is easy to parse. I would recommend replacing the entire code with:

$servers = json_decode(shell_exec('gcloud compute instances list --format=json'), true);

… to take advantage of json_decode(). That's it — you're done!

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for your feedback didn't know on the --foramt=json with GCE will be a great help in the future , this code is more to general purpose usage to transform linux shell out put to array, not a GCE output convertor \$\endgroup\$
    – Meta_data
    Nov 16, 2017 at 7:31
  • \$\begingroup\$ On Code Review, we assume that everything you post is your real code, since it would be impossible (and off-topic) to review hypothetical code. I encourage you to post a separate question about parsing the output of a different Linux command. \$\endgroup\$ Nov 16, 2017 at 15:02
1
\$\begingroup\$

Simplification for parsing headers and removing from array

$headers = turn_linux_row_to_array($data[0]);
unset($data[0]);

This could be simplified using array_shift() to pop the first row out of the array of rows, and preg_split() to separate header names by 1 or more white space characters:

$headers = preg_split('#\s+#',array_shift($data)); 

That way turn_linux_row_to_array() doesn't have to handle the headers. Actually, the for loop within that method could be replaced with a foreach- then the index would be increased automatically. Additionally, the code in that function could be moved back out to the foreach iterating over the rows. The entire code could be simplified like below:

$data = explode("\n",$result);
$headers = preg_split('#\s+#',array_shift($data));
foreach($data as $row){
    $server_data = array();
    $words = preg_split('#\s+#',$row);
    foreach($words as $word_position => $word) {
        if (trim($word)) {
            $server_data[$headers[$word_position]] = $word;
        }
    }
    if(!empty($server_data)){
        $servers_array[] = $server_data;
    }
}

See a demonstration in this Playground example.

Functional approach to iterating over rows

One could also replace the foreach loop on the rows with a call to array_map() in order to get the array of words for each row (and pass that to array_filter() to eliminate empty rows), though the use statement would be required in order to access $headers within the callback.

$servers_array = array_filter(array_map(function($row) use ($headers) {
    $server_data = array();
    $words = preg_split('#\s+#',$row);
    foreach($words as $word_position => $word) {
        if (trim($word)) {
            $server_data[$headers[$word_position]] = $word;
        }
    }
    return $server_data;
}, $data));
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.