るつぼっと

NWエンジニアな人たちに向けて

[Ansible] 変数のテキスト出力をパディングする方法

はじめに

Ansibleでテキストを生成する際に、可変長の変数を使っていても出力をきれいに
そろえる方法を調べたためここに書き残しておく。

予備知識

変数の表示フォーマットはJinja2経由でPythonのメソッドを使うことで定義できる。

Template Designer Documentation — Jinja Documentation (3.1.x)

.formatを使う方法と%を使う方法の2種類があるものの推奨は前者のようだ。
後者は以下のPythonのドキュメントからも分かるとおり古い表現方法らしい。

7. 入力と出力 — Python 3.10.6 ドキュメント

組み込み型 — Python 3.10.6 ドキュメント

よって今回は.formatを使ってみる。

基本的な使い方

まずは桁ぞろえの前に基本的な使い方から学ぶ。公式ドキュメントはこのあたり。

組み込み型 — Python 3.10.6 ドキュメント

string --- 一般的な文字列操作 — Python 3.10.6 ドキュメント

"{ 変数0の表示箇所 } { 変数1の表示箇所 }".format( 変数0,変数1 ) と使うようだ。


続いてフォーマットの指定については下記のあたりを参照。
string --- 一般的な文字列操作 — Python 3.10.6 ドキュメント

例えば数字の桁数を指定したい場合は
" { 0:4d } { 1:2d}".format( var_year,var_month ) のようにすればいいらしい。

以上を踏まえたPlaybook例がこちら。

---
- hosts: localhost
  gather_facts: false
  vars:
    date:
      - 2022
      - 7
      - 1
      - 31
  tasks:
    - name: print
      debug:
        msg: 
          # 基本形。{}のなかの数字は、format()内に指定した変数群の幾つ目を使うかを指定するもの
          - "{{ 'date1... {0}/{1}/{2}'.format(date[0],date[1],date[2]) }}"
          # format()の変数を左から順に使う場合はこのように{}内の数字を省略可能
          - "{{ 'date2... {}/{}/{}'.format(date[0],date[1],date[2]) }}"
          # このように同じ変数を複数回使いまわす場合は{}内に数字を入れたほうが簡略になる
          - "{{ 'date3... {0}/{1}/{2} - {0}/{1}/{3}'.format(date[0],date[1],date[2],date[3]) }}"
          - "{{ 'date4... {}/{}/{} - {}/{}/{}'.format(date[0],date[1],date[2],date[0],date[1],date[3]) }}"
          # フォーマットを指定したい場合、{}内の数字の後に:で区切って形式を定義する
          - "{{ 'date5... {0:04d}/{1:02d}/{2:02d}'.format(date[0],date[1],date[2]) }}"
          # フォーマットを指定しつつ、date2のように数字を省略した表記方法
          - "{{ 'date6... {:04d}/{:02d}/{:02d}'.format(date[0],date[1],date[2]) }}"

実行結果

TASK [print] *****************************************************
ok: [localhost] => 
  msg:
  - date1... 2022/7/1
  - date2... 2022/7/1
  - date3... 2022/7/1 - 2022/7/31
  - date4... 2022/7/1 - 2022/7/31
  - date5... 2022/07/01
  - date6... 2022/07/01

発展形

先ほどの例では0で数字の桁を揃えたが、これでは使う場面が限定される。
ここからは0以外を使って、かつ変数の右側へのパディングも試してみる。
それには<>を使う。例を見て頂くのが早い。

- hosts: localhost
  gather_facts: false
  vars:
    product:
      - name: apple
        value: 100
      - name: carrot
        value: 1000
  tasks:
    - name: print
      debug:
        msg: 
          - | # まずは何も調整せず出力する例
            {% for item in product %}
            {{ item.name }} {{ item.value }}
            {% endfor %}
          - | # <で変数右側へ半角スペースをパディング。10とすると、全体の文字数が10になるまで足される
            {% for item in product %}
            {{ '{:<10} {:<10}'.format(item.name,item.value) }}
            {% endfor %}
          - | # >で変数左側へ半角スペースをパディング
            {% for item in product %}
            {{ '{:>8} {:>6}'.format(item.name,item.value) }}
            {% endfor %}
            
          - | # >の後に文字を定義すると、その文字でパディングする。これは「0」でのパディング
            {% for item in product %}
            {{ '{:>8} {:>06}'.format(item.name,item.value) }}
            {% endfor %}
            
          - | # >の後に文字を定義すると、その文字でパディングする。これは「_」でのパディング
            {% for item in product %}
            {{ '{:_<10} {:<10}'.format(item.name,item.value) }}
            {% endfor %}

実行結果

TASK [print] *******************************************
ok: [localhost] => 
  msg:
  - |-
    apple 100
    carrot 1000
  - |-
    apple      100
    carrot     1000
  - |2-
       apple    100
      carrot   1000
  - |2-
       apple 000100
      carrot 001000
  - |-
    apple_____ 100
    carrot____ 1000

おまけ

引用したPythonのドキュメントを見るとわかるとおり他にも色々とできる。
例えば数値にカンマを入れたり、小数点以下の桁数を指定したり。

- hosts: localhost
  gather_facts: false
  tasks:
    - name: print
      debug:
        msg: 
          - "{{ '{:.2f}'.format(1/3) }}"  # 小数点以下を2桁にする
          - "{{ '{:,d}'.format(1000) }}" # 3桁ごとにカンマが入る表記

実行結果

TASK [print] **********************************************
ok: [localhost] => 
  msg:
  - '0.33'
  - 1,000

感想

Ansibleを使ってても時折Pythonの知識は必要になるんだなぁ。