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;
}
2 Answers 2
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!
-
\$\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– Meta_data2017年11月16日 07:31:41 +00:00Commented 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\$200_success– 200_success2017年11月16日 15:02:33 +00:00Commented Nov 16, 2017 at 15:02
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));
Explore related questions
See similar questions with these tags.