76

In ansible, I need to check whether a particular line present in a file or not. Basically, I need to convert the following command to an ansible task. My goal is to only check.

grep -Fxq "127.0.0.1" /tmp/my.conf
mcvkr
  • 3,209
  • 6
  • 38
  • 63
Ansuman Bebarta
  • 7,011
  • 6
  • 27
  • 44

9 Answers9

95

Use check_mode, register and failed_when in concert. This fails the task if the lineinfile module would make any changes to the file being checked. Check_mode ensures nothing will change even if it otherwise would.

- name: "Ensure /tmp/my.conf contains '127.0.0.1'"
  lineinfile:
    name: /tmp/my.conf
    line: "127.0.0.1"
    state: present
  check_mode: yes
  register: conf
  failed_when: (conf is changed) or (conf is failed)
LHSnow
  • 1,146
  • 9
  • 12
  • 1
    For ansible 2.2 use `name:` instead of `path:` – kubanczyk May 23 '17 at 13:39
  • 1
    This way you can use the same task for checking and later deploying the change, just with different values for `check_mode` and `failed_when`. – nlu Mar 22 '18 at 16:43
  • 15
    I used this approach, but set `state: absent` and used the `regexp` parameter rather than `line`. That gives you pretty much all the flexibility of using grep, and the value of `changed` in the registered variable equates to whether the pattern was found or not. – robo Jun 14 '18 at 15:53
  • 1
    This can also be nicely combined with a `with_x` ansible loop. – Jan Christoph Terasa Jan 08 '19 at 17:51
57
- name: Check whether /tmp/my.conf contains "127.0.0.1"
  command: grep -Fxq "127.0.0.1" /tmp/my.conf
  register: checkmyconf
  check_mode: no
  ignore_errors: yes
  changed_when: no

- name: Greet the world if /tmp/my.conf contains "127.0.0.1"
  debug: msg="Hello, world!"
  when: checkmyconf.rc == 0

Update 2017-08-28: Older Ansible versions need to use always_run: yes instead of check_mode: no.

Antonis Christofides
  • 6,990
  • 2
  • 39
  • 57
46

User robo's regexp & absent method is quite clean, so I've fleshed it out here for easy use and added improvements from comments by @assylias and @Olivier:

- name: Ensure /tmp/my.conf contains 127.0.0.1
  ansible.builtin.lineinfile:
    path: /tmp/my.conf
    regexp: '^127\.0\.0\.1.*whatever'
    state: absent
  check_mode: yes
  changed_when: false
  register: out

- debug:
    msg: "Yes, line exists."
  when: out.found

- debug:
    msg: "Line does NOT exist."
  when: not out.found
4wk_
  • 2,458
  • 3
  • 34
  • 46
Donn Lee
  • 2,962
  • 1
  • 24
  • 16
  • 3
    It works but the downside is that the first task has a "changed" status when the line is present. This can be fixed by adding `changed_when: false`. – assylias Apr 18 '19 at 20:50
  • 2
    For me the best solution is his answer, with assylias comment (`changed_when: false`), and then use `out.found` to check if a line was found. – Olivier Oct 03 '19 at 12:34
  • @Olivier: I've updated the answer with `changed_when: false` and `out.found`. ty – Donn Lee Oct 04 '19 at 17:53
  • 1
    I get `'dict object' has no attribute 'found'` – Random Guy Jan 11 '22 at 13:28
  • @DanielVazome the registered variable won't have the `found` key if it either wasn't able to find the file itself or didn't have access to it, e.g. perhaps the task required a `become`. – timss Feb 10 '22 at 15:43
  • Can anyone link me where in the Ansible docs it explains this `found` key? – Rino Bino Jan 06 '23 at 17:44
  • Much better than shell/command. Thank you! – TJ Zimmerman Mar 16 '23 at 22:09
  • @RinoBino the `found` key is in the result registered variable of the `lineinfile` module. Just use Ansible `ansible.builtin.debug module` module with `var: out`, and you will see that out variable containt a key named `found`. If it's 0, then the pattern is not found. – 4wk_ Mar 29 '23 at 08:29
14

With the accepted solution, even though you ignore errors, you will still get ugly red error output on the first task if there is no match:

TASK: [Check whether /tmp/my.conf contains "127.0.0.1"] ***********************
failed: [localhost] => {"changed": false, "cmd": "grep -Fxq "127.0.0.1" /tmp/my.conf", "delta": "0:00:00.018709", "end": "2015-09-27 17:46:18.252024", "rc": 1, "start": "2015-09-27 17:46:18.233315", "stdout_lines": [], "warnings": []}
...ignoring

If you want less verbose output, you can use awk instead of grep. awk won't return an error on a non-match, which means the first check task below won't error regardless of a match or non-match:

- name: Check whether /tmp/my.conf contains "127.0.0.1"
  command: awk /^127.0.0.1$/ /tmp/my.conf
  register: checkmyconf
  changed_when: False

- name: Greet the world if /tmp/my.conf contains "127.0.0.1"
  debug: msg="Hello, world!"
  when: checkmyconf.stdout | match("127.0.0.1")

Notice that my second task uses the match filter as awk returns the matched string if it finds a match.

The alternative above will produce the following output regardless of whether the check task has a match or not:

TASK: [Check whether /tmp/my.conf contains "127.0.0.1"] ***********************
ok: [localhost]

IMHO this is a better approach as you won't ignore other errors in your first task (e.g. if the specified file did not exist).

mixja
  • 6,977
  • 3
  • 32
  • 34
6

Use ansible lineinfile command, but this command will update the file with the line if it does not exists.

- lineinfile: dest=/tmp/my.conf line='127.0.0.1' state=present
VSK
  • 459
  • 4
  • 11
  • I agree to Antonis Christofides, I think you will have to use the command module for this purpose. Any alternative seems to be a work around than an ansible solution. – VSK Jun 12 '15 at 08:44
  • 25
    It would be nice if a state=check was available in the `lineinfile` module – mixja Sep 27 '15 at 05:04
5

Another way is to use the "replace module" then "lineinfile module".

The algo is closed to the one used when you want to change the values of two variables.

  • First, use "replace module" to detect if the line you are looking for is here and change it with the something else. (Like same line + something at the end).
  • Then if "replace" is true, It means your line is here then replace the new line with a particularity, with the new line looking.
  • Else the line you are looking for is not here.

Example:

# Vars
- name: Set parameters
  set_fact:
    newline      : "hello, i love ansible"
    lineSearched : "hello"
    lineModified : "hello you"

# Tasks
- name: Try to replace the line
  replace:
    dest    : /dir/file
    replace : '{{ lineModified }} '
    regexp  : '{{ lineSearched }}$'
    backup  : yes
  register  : checkIfLineIsHere

- name: Line is here, change it
  lineinfile:
    state   : present
    dest    : /dir/file
    line    : '{{ newline }}'
    regexp  : '{{ lineModified }}$'
  when: checkIfLineIsHere.changed
  • If the file contains "hello", it will become "hello you" then "hello, i love ansible" at the end.
  • If the file content doesn't contain "hello", the file is not modified.

With the same idea, you can do something if the lineSearched is here:

# Vars
- name: Set parameters
  set_fact:
    newline      : "hello, i love ansible"
    lineSearched : "hello"
    lineModified : "hello you"

# Tasks
- name: Try to replace the line
  replace:
    dest    : /dir/file
    replace : '{{ lineModified }} '
    regexp  : '{{ lineSearched }}$'
    backup  : yes
  register  : checkIfLineIsHere

# If the line is here, I want to add something.
- name: If line is here, do something
  lineinfile:
    state   : present
    dest    : /dir/file
    line    : '{{ newline }}'
    regexp  : ''
    insertafter: EOF
  when: checkIfLineIsHere.changed

# But I still want this line in the file, Then restore it
- name: Restore the searched line.
  lineinfile:
    state   : present
    dest    : /dir/file
    line    : '{{ lineSearched }}'
    regexp  : '{{ lineModified }}$'
  when: checkIfLineIsHere.changed
  • If the file contains "hello", the line will still contain "hello" and "hello, i love ansible" at the end.
  • If the file content doesn't contain "hello", the file is not modified.
Je.Go
  • 81
  • 1
  • 7
  • Nice example ! As some might argue it is a lot complexer than using command module, but I prefer ansible solutions, even when there not always obvious at first. I'm trying to use the capture groups like in sed to get a specific string from a file. Any ideas ? for example `sed -n 's:secret\ \"\(.*\)\".*:\1:p' tsig-key` with line in file – oneindelijk Jul 24 '20 at 12:25
2

You can use the file plugin for this scenario.

To set a fact you can use in other tasks ... this works.

- name: Check whether /tmp/my.conf contains "127.0.0.1"
  set_fact:
    myconf: "{{ lookup('file', '/tmp/my.conf') }}"  
  ignore_errors: yes

- name: Greet the world if /tmp/my.conf contains "127.0.0.1"
  debug: msg="Hello, world!"
  when: "'127.0.0.1' in myconf"

To check the file content as a condition of a task ... this should work.

- name: Greet the world if /tmp/my.conf contains "127.0.0.1"
  debug: msg="Hello, world!"
  when: "'127.0.0.1' in lookup('file', '/tmp/my.conf')"
Robert Colbert
  • 126
  • 1
  • 2
  • 4
    This will only work for local file on the ansible controller machine as lookups always run on the controller. To use this method for remote files, you would have to copy/slurp them locally before. – Zeitounator Jan 08 '20 at 16:56
1

Another solution, also useful for other purposes is to loop over the contents of the file, line by line

- name: get the file
  slurp:
    src: /etc/locale.gen
  register: slurped_file

- name: initialize the matches list
  set_fact:
    MATCHES: []

- name: collect matches in a list
  set_fact:
    MATCHES: "{{ MATCHES + [line2match] }}"
  loop: "{{ file_lines }}"
  loop_control:
    loop_var: line2match
  vars:
    - decode_content: "{{ slurped_file.content | b64decode }}"
    - file_lines: "{{ decode_content.split('\n') }}"
  when: '"BE" in line2match'
  
- name: report matches if any
  debug:
    msg: "Found {{ MATCHES | length }} matches\n{{ MATCHES }}"
  when: 'listlen | int > 0'
  vars:
    listlen: "{{ MATCHES | length }}"

This mechanism can be use to get a specific value from a matched line if you use it for example with the jinja regex_replace filter

oneindelijk
  • 606
  • 1
  • 6
  • 18
0

The question shows grep with -Fx which means a whole line fixed string. But many people will land here looking for a way to do it with a regex.

Option 1 using shell (simpler):

- ansible.builtin.shell: grep '.* bar baz' /some/file
  changed_when: false

Option 2 using ansible itself (for the purists, and Windows users):

- ansible.builtin.lineinfile:
    path: /some/file
    regex: ".* bar baz"
    state: absent            # here's the trick
  changed_when: false
  check_mode: true
  register: result
  failed_when: result.found != 1
lonix
  • 14,255
  • 23
  • 85
  • 176