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], ", nrtvx00");
}
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' => +([^,]*).*/1n/" foo.sh | head -1
The result is
‘abc’
Working two-dimensional command:
sed -Ez ":a;s/.*g[^]]*h' => +([^,]*).*/1n/" foo.sh | head -1
The result is
‘i’
Three-dimensional command:
sed -Ez ":a;s/.*Realtime*[^]]*a[^]]*b' => +([^,]*).*/1n/" 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' => +([^,]*).*/1n/" 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.
3
Answers
One
awk
idea:This generates:
NOTE: this solution is hardcoded based on provided sample
Assuming the string
Realtime
occurs only once in the file, you can try this GNUsed
EDIT
Working code for OP’s use case
Based on your test with
tr
andgrep
:Notes:
Realtime
block in your file,| head -1
if not necessaryUPDATE
More generic solution:
You cloud add level filter between 1 and N (the last) and/or any filter between each
grep
commands