skip to Main Content

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


  1. One awk idea:

    awk -F"'" '                                             # define input field delimiter as single quote
    $2 == "Realtime"           { inblock=1 }                # if 2nd field == "Realtime" then set flag
    $2 == "enabled" && inblock { if (NF==3) {               # value is not wrapped in single quotes
                                    pos=index($0,"=>")      # find location of "=>" string
                                    value=substr($0,pos+2)  # grab everything after "=>"
                                    gsub(/[ ,]/,"",value)   # remove all spaces and commas
                                 }
                                 else                       # value is wrapped in single quotes
                                    value=$4                # grab 4th field
                                 print value
                                 exit                       # no need to process rest of file so exit script
                               }
    ' myconfig.php
    

    This generates:

    true
    

    NOTE: this solution is hardcoded based on provided sample

    Login or Signup to reply.
  2. Assuming the string Realtime occurs only once in the file, you can try this GNU sed

    $ sed -Ez "s/.*Realtime[^]]*enabled' => +([^,]*).*/1n/" myconfig.php
    true
    

    EDIT

    Working code for OP’s use case

    $ sed -Ez "s/.*Realtime*[^]]*[^[]*a[^]]*b' => +([^,]*).*/1n/" foo.sh | head -1
    
    Login or Signup to reply.
  3. Based on your test with tr and grep:

    #! /usr/bin/env bash
    
    tr -d "n" < "myconfig.php" 
    | grep -o "'Realtime' => [[^]*]" 
    | grep -oE "'enabled' => (true|false)" 
    | head -1 
    | cut -d " " -f 3
    

    Notes:

    • If you have only one Realtime block in your file, | head -1 if not necessary
    • If you are not sure about number of spaces, remove all spaces and changes next filters like this:
    #! /usr/bin/env bash
    
    tr -d "n" < "myconfig.php" 
    | tr -d " " 
    | grep -o "'Realtime'=>[[^]*]" 
    | grep -oE "'enabled'=>(true|false)" 
    | head -1 
    | cut -d ">" -f 2
    

    UPDATE

    More generic solution:

    #! /usr/bin/env bash
    
    INPUT_FILE="$1"
    LEVEL_1_OBJ="Settings"
    LEVEL_N_OBJ="Realtime"
    FIELD="enabled"
    
    tr -d "n" < "${INPUT_FILE}" 
    | tr -d " " 
    | grep -o "'${LEVEL_1_OBJ}'=>[.*" 
    | grep -o "'${LEVEL_N_OBJ}'=>[[^]*]" 
    | grep -oE "'${FIELD}'=>[^,]*" 
    | head -1 
    | cut -d ">" -f 2
    

    You cloud add level filter between 1 and N (the last) and/or any filter between each grep commands

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search