3

I have this file, and I'd like to select all the IP addresses under the masters/hosts section if their line is not commented. I tried this sed 's/.*\ \([0-9\.]\+\).*/\1/g', but it did not work.

metal:
  children:
    masters:
      hosts:
        dev0: {ansible_host: 172.168.1.10}
        # dev1: {ansible_host: 185.168.1.11}
        # dev2: {ansible_host: 142.168.1.12}
    workers:
      hosts: {}
        # dev3: {ansible_host: 172.168.1.13}

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
Jackson
  • 215
  • 1
  • 2
  • 7

2 Answers2

14

The same way that dealing with JSON is better handled in shell with an adequate tool: jq, what if there was a similar tool for YAML?

There is actualy a jq wrapper called... yq that can be used like jq with YAML input. It doesn't appear to be packaged in distributions. Anyway once this python command is built and installed (along the mandatory jq tool which is widely available), one can now parse accordingly the YAML file, since it will naturally ignore comments.

yq -r '.metal.children.masters.hosts[].ansible_host' < myfile.yaml

which will dump IP addresses in masters as long as the syntax is valid.

A.B
  • 36,364
  • 2
  • 73
  • 118
2

A sed solution:

sed -n '/masters:/n;/hosts:/{:a n;/^ *#* *dev[0-9]/!d;s/^ *dev[0-9]: {ansible_host: //;tl;ba;:l s/}//p;ba}' test

in a multiline fashion :

    sed -n '
         /masters:/n
         /hosts:/{
             :a n
             /^ *#* *dev[0-9]/!d
             s/^ *dev[0-9]: {ansible_host: //
             tl
             ba
             :l 
                  s/}//p
                  ba
         }' test

The sed cmd do the following :

sed : /bin/sed the executable
-n : sed option to avoid auto printing
/masters:/n : If the current line is "master:" read the **n**ext line in the pattern space
/hosts:/{ : If the current line, the one read before, is "hosts:" do the following command block
:a n : Create a label called "a" and read the next line in the patter space.
/^ *#* *dev[0-9]/!d : If the current line, is **not** a dev[0-9] keyword (even commented) then delete current line and start a new cicle (exit from the loop)
s/^ *dev[0-9]: {ansible_host: // : sobstitute anything in the line except the ip address.
tl : If the preceding sobstitution succeded, then jump to the "l" label (skipping the next instruction).
ba : Is a commented line: Jump to the "a" label and read the next line.
:l : Create a new label called "l"
s/}//p : Remove the last "}" and print the pattern space (The ip address)
ba : Jump to label "a" to process the next line.
} : Close the code block opened by the match on the "host" line.


Or, in awk:

awk '
    /masters:/{
        f=1
        next
    }
    /hosts:/ && f {
        f++
        next
    }
    /^ *dev[0-9]: [{]ansible_host: / && f == 2 {
         sub(/[}]$/, "", $NF)
         print $NF
         next
    }
    !/^ *#? ?dev[0-9]*:/ && f==2{f=0}
' test

With perl and YAML module :

perl -MYAML -le '
    $y = YAML::LoadFile "test";
    %h = %{$y->{metal}->{children}->{masters}->{hosts}};
     print values $h{$_}->%* for (keys %h)
'

Use %{$h{$_}} instead of $h{$_}->%* if your perl version does not support it.