Issue
I have a long config file which looks like:
<some stuff before our example>
'Realtime' => [
'foo' => 'bar',
'enabled' => true,
'lorem' => 'ipsum'
],
<some stuff after our example>
The above is a large config php file and I was asked to mine the enabled
value of 'Realtime` with bash. I could do it with PHP, but I was specifically asked to do it with bash.
I tried the following:
echo $(tr '\n' ' ' < myconfig.php | sed '$s/ $/\n/') | grep -o -P '(?<=Realtime).*(?=\])'
and this mines the text from the file between Realtime
and the last ]
. But I would like to mine the content between Realtime
and the first ]
. For the time being I have implemented a simplistic bash and accompanied that with PHP parser, as follows:
public function getConfig($name)
{
$path = Paths::CONFIG_FILE;
if (!$this->config) {
$this->config = Command_ShellFactory::makeForServer('zl', "cat {$path}")->execute(true, true);
}
$splitName = explode('.', $name);
$lastPosition = 0;
$tempConfig = $this->config;
foreach ($splitName as $currentName) {
if (($position = strpos($tempConfig, $currentName)) === false) {
throw new RuntimeException('Setting was not found');
}
$tempConfig = substr($tempConfig, $position);
}
return trim(explode("=>", explode("\n", $tempConfig)[0])[1], ", \n\r\t\v\x00");
}
and this works, but I'm not satisfied with it, because it loads the whole file into memory via the shell command and then searches for the nested key (Realtime.enabled
is passed to it). Is it possible to improve this code in such a way that all the logic would happen via bash, rather than helping it with PHP?
EDIT
The possible settings to mine could be of any depth. Examples:
[
/*...*/
'a' => 'b', //Depth of 1
'c' => [
'a' => 'd' //Depth of 2
],
'e' => [
'f' => [
'g' =>'h' //Depth of 3
]
]
/*...*/
]
Theoretically any amount of depth is possible, in the example we have a depth of 1, a depth of 2 and a depth of 3.
EDIT
I have created foo.sh (some fantasy name of no importance):
[
'Realtime' => [
'enabled' => [
'd' => [
'e' => 'f'
],
],
'a' => [
'b' => 'c'
],
]
'g' => [
'h' => 'i'
],
'Unrealtime' => 'abc'
]
Working one-dimensional command:
sed -Ez ":a;s/.*Unrealtime' => +([^,]*).*/\1\n/" foo.sh | head -1
The result is
'abc'
Working two-dimensional command:
sed -Ez ":a;s/.*g[^]]*h' => +([^,]*).*/\1\n/" foo.sh | head -1
The result is
'i'
Three-dimensional command:
sed -Ez ":a;s/.*Realtime*[^]]*a[^]]*b' => +([^,]*).*/\1\n/" foo.sh | head -1
It is working if and only if the
'a' => [
'b' => 'c'
]
is the first child of Realtime
. So, something is missing, as I need to avoid assuming that the element I search for is the first child.
Working four-dimensional command:
sed -Ez ":a;s/.*Realtime[^]]*enabled[^]]*d[^]]*e' => +([^,]*).*/\1\n/" foo.sh | head -1
Again, it only works if enabled
is the first child of Realtime
. I was modifying my test case above, changing the order of the children of Realtime
. So, it seems that the only thing missing from this expression is something that would specify that we are not necessarily looking for the first child.
Solution
Assuming the string Realtime
occurs only once in the file, you can try this GNU sed
$ sed -Ez "s/.*Realtime[^]]*enabled' => +([^,]*).*/\1\n/" myconfig.php
true
EDIT
Working code for OP's use case
$ sed -Ez "s/.*Realtime*[^]]*[^[]*a[^]]*b' => +([^,]*).*/\1\n/" foo.sh | head -1
Answered By - HatLess Answer Checked By - Marilyn (WPSolving Volunteer)