はじめに
Ansibleではファイルの中身を編集するモジュールとして、lineinfileやreplaceモジュールなどが用意されています。
自分はよくlineinfileを使用していますが、使い方を忘れることが多いため、検証した結果をまとめたいと思います。
lineinfile
lineinfileモジュールでは、ファイルの特定の行に対して、追加/更新/削除を行うことができます。
主に、ファイルの1行のみを変更する際によく使われます。
動作確認
追記/変更/削除の各パターンで動作確認を行っていきます。
変更対象のファイルはhostsファイルを用意して処理を行っています。
※ansible [core 2.20.1]で動作確認をしています。
行追記
1. ファイルの末尾に新しい行を追加
下記のhostsファイルに192.168.0.1 example.comを追加します。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
その場合は以下のようにして実装が可能です。
パラメータで、文字列を挿入する箇所を指定できますが、未指定の場合はファイル末尾に追加されます。
---
- name: Test Playbook
hosts: all
gather_facts: false
tasks:
- name: 追記確認
ansible.builtin.lineinfile:
path: /work/ansible/hosts
line: "192.168.0.1 example.com"
state: present
Playbook実行後は下記のように、ファイル末尾へlineで指定した値が挿入されます。
改行は付与されないので、必要な場合はshellモジュールなどを使って追加する必要があります。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
192.168.0.1 example.com
こちらは冪等性が担保されており、複数回実行しても同じ行が追加されていく…といったことはありません。

2. 特定の行の下に新しい行を追加
下記のhostsファイルの# 192.168.1.13 bar.example.org barの下に
192.168.0.254 gateway.comを追加します。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
192.168.0.1 example.com
Playbookは下記のようになります。
insertafterパラメータを利用して特定の行の後に文字列を挿入できます。
※正規表現が使えるため完全一致でなくても大丈夫です。
複数行にマッチした場合は最後にマッチした行が適用されますが、firstmatchパラメータを利用することで、最初にマッチした行の後ろに挿入させることも可能です。
もし、insertafterで一致する行がなかった場合は、最下行に追記されます。
---
- name: Test Playbook
hosts: all
gather_facts: false
tasks:
- name: 追記確認
ansible.builtin.lineinfile:
path: /work/ansible/hosts
line: "192.168.0.254 gateway.com"
insertafter: "^#\\s192.168.1.13.*$"
state: present
実行後のhostsファイルは下記の通りです。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
192.168.0.254 gateway.com
192.168.0.1 example.com
こちらも冪等性が担保されており、複数回実行しても同じ行が追加されていく…といったことはありません。
注意点として、insertafterで指定した部分ではないところに行が存在しても(ファイルのどこかしらに行が存在する場合)、処理はスキップされてしまいます。

3. 複数行追記
下記のようにlineパラメータ内に改行コードを含めることで、複数行追記できますが、
この場合は冪等性がなくなり、常に行が追加されてしまいます。
blockinfileモジュールを使用するか、lineinfileでloopを使うなどして対応しましょう。
---
- name: Test Playbook
hosts: all
gather_facts: false
tasks:
- name: 追記確認
ansible.builtin.lineinfile:
path: /work/ansible/hosts
line: "192.168.0.254 gateway.com\n192.168.0.10 test.com"
state: present
行変更
1. 特定の行を変更
下記のhostsファイルの192.168.0.5 example.comを
192.168.0.55 foo.comに変更します。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
192.168.0.5 example.com
Playbookは下記のようになります。
regexpパラメータで変更対象の行を検索し、lineで指定した値に置き換えます。
一致する行がない場合は、変更は加わりません。
---
- name: Test Playbook
hosts: all
gather_facts: false
tasks:
- name: 変更確認
ansible.builtin.lineinfile:
path: /work/ansible/hosts
line: '192.168.0.55 foo.com'
regexp: '^192.168.0.5\s.*$'
state: present
playbook実行後のhostsファイルは以下のように指定した行が置き換わります。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
192.168.0.55 foo.com
注意点として、regexpで複数行一致する場合は、最後の行のみ変更がかかります。
※最初の行のみにしたい場合はfirstmatchパラメータを利用
すべての行を変更したい場合は、untilを利用して、changed: false となるまで繰り返すのがよいかと思います。
2. 特定の行を変更(存在しない場合に追記)
regexpパラメータは行の置き換えで使用しますが、一致する行がなかった場合は何も処理を行いません。
存在しない場合に、行を追加する場合はinsertafterパラメータを併用します。
下記では、noneという行が存在する場合は、192.168.0.100 hoge.comに置き換え、
存在しなければ、#\s192.168.1.13.*$に合致する行の下に追記します。
---
- name: Test Playbook
hosts: all
gather_facts: false
tasks:
- name: 変更確認
ansible.builtin.lineinfile:
path: /work/ansible/hosts
line: '192.168.0.100 hoge.com'
regexp: 'none'
insertafter: '^#\s192.168.1.13.*$'
state: present
Playbookの実行後のhostsファイルは以下のようになります。
none という行は存在しないため、lineに記載した文字列が挿入されます。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
192.168.0.100 hoge.com
192.168.0.5 example.com
3. 特定の行の一部を変更
下記のhostsファイルの192.168.0.5 example.comの行にて
IP部分のみ172.0.0.5に変更します。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
192.168.0.5 example.com
Playbookは下記のようにして対応が可能です。
ポイントは下記となります。
-
backrefsパラメータを有効化 -
regexpで再利用したい箇所を括弧でキャプチャ -
lineでキャプチャしたものを再利用(\1, \2, ...)
---
- name: Test Playbook
hosts: all
gather_facts: false
tasks:
- name: 変更確認
ansible.builtin.lineinfile:
path: /work/ansible/hosts
line: '172.16.0.5\1'
regexp: '^192\.168\.0\.5(\s+\S+)$'
backrefs: true
state: present
Playbookを実行した後のhostsファイルは下記になります。
想定通りIP部分のみ置き換えられています。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
172.16.0.5 example.com
行全体の置き換えで対応できるケースの方が多いと思いますが、コメントアウトしたい場合や空白の数を変えたくないといった際には重宝します。
ただ、ぱっと見で何をしているのか把握しにくいので、READMEやコメントなどで補足を入れるようにすると親切かと思います。
行削除
1. 特定行の削除
下記のhostsから192.168.で始まるエントリーを削除します。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
192.168.0.100 hoge.com
192.168.0.5 example.com
172.16.0.5 fuga.com
172.16.0.100 foo.com
Playbookは下記のようになります。
regexpで削除対象の行を検索して、一致した行を削除します。
---
- name: Test Playbook
hosts: all
gather_facts: false
tasks:
- name: 削除確認
ansible.builtin.lineinfile:
path: /work/ansible/hosts
regexp: '^192\.168\..*$'
state: absent
Playbookの実行後のhostsファイルは以下です。
192.168.で始まるエントリーがすべて削除されました。
# Loopback entries; do not change.
# For historical reasons, localhost precedes localhost.localdomain:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# See hosts(5) for proper format and other examples:
# 192.168.1.10 foo.example.org foo
# 192.168.1.13 bar.example.org bar
172.16.0.5 fuga.com
172.16.0.100 foo.com
行の変更時は、1行しか行われないのに対し、削除は合致したものすべて削除されます。
※実行結果からも1回で複数の行が削除されていることがわかります。
行変更時のような、繰り返し処理は不要となることを覚えておくとよいかなと思います。

まとめ
今回はlineinfileモジュールの挙動を確認しました。
誰かのお役に立てれば幸いです。