user8888888888888888
user8888888888888888

Reputation: 43

How to read a block of text from a file in ansible

Hi I am reading content from a file.

The file contains the following content.

======

interface And

public void and(int, int);

public void nand(int, int);

======

interface Or

 public void or(int, int);

 public void nor(int, int);

======

interface Xor

 public void xor(int, int);

 public void xnor(int, int);

======

interface Not

 public void not(int);

======

class BitWise extends And, Or, Xor, Not

// Implementation of the Interfaces goes here

======

I am trying to read only the interfaces

I went through this How to read a particular part of file in ansible

---
 - name: Read the Interfaces
   hosts: 127.0.0.1
   connection: local
   vars:
      - file_path: " {{ playbook_dir }}/input_file.txt"
      - my_interfaces: []
   tasks:
         - name: Reading the interfaces
           set_fact:
                   my_interfaces: "{{ my_interfaces + [ item ] }}"
           with_lines: "cat {{file_path}}"
           when: item is search('^interface.+?=*')
         - name: Printing all the interfaces
           debug:
                  var: my_interfaces

The Programs Output is

ok: [127.0.0.1] => {
    "my_interfaces": [
        "interface And",
        "interface Or",
        "interface Xor",
        "interface Not"
    ]
}

But The desired output is

ok: [127.0.0.1] => {
    "my_interfaces": [
        "interface And \n public void and(int, int) \n public void nand(int, int)",
        "interface Or \n public void or(int, int) \n public void nor(int, int)",
        "interface Xor \n public void xor(int, int) \n public void xnor(int, int)",
        "interface Not \n public void not(int)",
    ]
}

I think that I am doing something wrong in the regular expression part. But I don't know how to correct it to get the desired output.Could anyone help me to solve the problem. And is there any other way than this to do the same task.

Upvotes: 2

Views: 2290

Answers (3)

Alassane Ndiaye
Alassane Ndiaye

Reputation: 4777

You can combine filters and tests to get your desired result:

---
- hosts: localhost
  gather_facts: false
  vars:
      content: "{{ lookup('file', 'filename') }}"
  tasks:
    - name: "split file into blocks"
      set_fact:
          content: "{{ content.split('======') }}"
    - debug:
          msg: "{{ content }}"
    - name: "remove white space from start and end of blocks"
      set_fact:
          content: "{{ content | map('trim') | list}}"
    - debug:
          msg: "{{ content }}"
    - name: "select blocks that start with interface"
      set_fact:
          content: "{{ content | select('search', '^interface') | list}}"
    - debug:
          msg: "{{ content }}"

You can also combine all the steps in a single command:

---
- hosts: localhost
  gather_facts: false
  vars:
      content: "{{ lookup('file', 'filename') }}"
  tasks:
    - name: "fetch interfaces"
      set_fact:
          content: "{{ content.split('======') | map('trim') | select('search', '^interface') | list }}"
    - debug:
          msg: "{{ content }}"

This will return:

[u'interface And\n\npublic void and(int, int);\n\npublic void nand(int, int);',
 u'interface Or\n\n public void or(int, int);\n\n public void nor(int, int);',
 u'interface Xor\n\n public void xor(int, int);\n\n public void xnor(int, int);',
 u'interface Not\n\n public void not(int);']

Upvotes: 2

The fourth bird
The fourth bird

Reputation: 163437

In your pattern ^interface.+?=* this part .+? is non greedy so the engine would match 1+ times as least as possible. This part =* matches an equals sign 0+ times.

When there is no equals sign in the interface, it would only match interface followed by a space if the dot does not match a newline.

You have to enable that the dot matches a newline (use an inline modifier (?s) if supported) if you want to use your pattern. Use a capturing group or a positive lookahead to not match the newline and the equals signs but make sure that it is there.

(?s)^interface\b.+?(?=\r?\n=)

Regex demo

Another option could be to match interface and the rest of the line. Then repeat matching lines as long as the next line does not start with an equals sign using a negative lookahead (?!=)

^interface\b.*(?:\r?\n(?!=).*)*

Regex demo

Upvotes: 0

Vladimir Botka
Vladimir Botka

Reputation: 68104

Once you have the list of the headlines in my_interfaces it would be possible to use sed and print the range of the lines.

The tasks below

- command: "sed -n '/{{ item }}/,/======/p' {{ file_path }}"
  register: result
  loop: "{{ my_interfaces }}"
- set_fact:
    my_ifc: "{{ my_ifc|default([]) + [ item.stdout_lines ] }}"
  loop: "{{ result.results }}"
- debug:
    var: my_ifc

give

"my_ifc": [
    [
        "interface And", 
        "", 
        "public void and(int, int);", 
        "", 
        "public void nand(int, int);", 
        "", 
        "======"
    ], 
    [
        "interface Or", 
        "", 
        " public void or(int, int);", 
        "", 
        " public void nor(int, int);", 
        "======"
    ], 
    [
        "interface Xor", 
        "", 
        " public void xor(int, int);", 
        "", 
        " public void xnor(int, int);", 
        "======"
    ], 
    [
        "interface Not", 
        "", 
        " public void not(int);", 
        "======"
    ]
]

(formatting wip ...)

Upvotes: 1

Related Questions