ansible筆記(39):jinja2模板(二)
- A+
所屬分類:ansible ofollow,noindex" target="_blank">運維技術
在本部落格中,ansible是一個系列文章,我們會盡量以通俗易懂的方式總結ansible的相關知識點。
ansible系列博文直達連結:ansible輕鬆入門系列
"ansible系列"中的每篇文章都建立在前文的基礎之上,所以, 請按照順序閱讀這些文章,否則有可能在閱讀中遇到障礙。
前一篇文章中我們提到過,在jinja2中,使用"{% %}"對控制語句進行包含,比如"if"控制語句、"for"迴圈控制語句等 都需要包含在"{% %}"中,那麼這篇文章我們就來聊聊"{% %}"。
先來聊聊if,與其他語言相同,if用來進行條件判斷,在jinja2中,if的語法如下:
{% if 條件 %} ... ... ... {% endif %}
如你所見,當使用if語法時,需要使用endif作為結束,示例模板如下:
# cat test.j2 jinja2 test {% if testnum > 3 %} greater than 3 {% endif %}
使用如下playbook對模板檔案進行渲染
注:不要使用ad-hoc命令呼叫template模組進行渲染,因為使用命令呼叫template模組時,無論你傳入的資料是哪種型別,都會被當做字串進行處理,所以此處使用playbook渲染模板,以保證資料型別的正確。
# cat temptest.yml --- - hosts: test70 remote_user: root gather_facts: no tasks: - template: src: /testdir/ansible/test.j2 dest: /opt/test vars: testnum: 5
最終生成的檔案內容如下:
# cat /opt/test jinja2 test greater than 3
很簡單吧,使用if進行條件判斷,能夠讓我們的模板變得更加靈活。
除了"if"結構,當然還有"if...else..."結構,語法如下:
{% if 條件 %} ... {% else %} ... {% endif %}
與其他語言一樣,也有"if...else if..."的語法結構,如下:
{% if 條件一 %} ... {% elif 條件二 %} ... {% elif 條件N %} ... {% endif %}
或者結合在一起使用,語法如下
{% if 條件一 %} ... {% elif 條件N %} ... {% else %} ... {% endif %}
上述語法就是"if"控制語句的語法。
其實,說到"if",不僅僅有"if"控制語句,還有"if"表示式,利用"if"表示式,可以實現類似三元運算的效果,示例模板內容如下:
# cat test.j2 jinja2 test {{ 'a' if 2>1 else 'b' }}
渲染後的檔案內容如下
# cat /opt/test jinja2 test a
如果你使用過其他語言的三目運算,或者你使用過python的三元運算,那麼你一定已經看明白了上述表示式的含義,上例中的if表示式的含義為,如果2>1這個條件為真,則使用'a',如果2>1這個條件不成立,則使用'b',而2必定大於1,所以條件成立,最終使用'a',在jinja2中,if表示式的語法如下:
<do something> if <something is true> else <do something else>
"if"表示式和"if"控制語句並不是一個東西,"if"表示式可以與其他的控制語句結合使用,我們後面再聊
在前文的示例中,我們都是在playbook中定義變數,然後在模板檔案中使用變數,其實,我們也可以直接在模板檔案中定義變數,示例如下:
# cat test.j2 jinja2 test {% set teststr='abc' %} {{ teststr }}
如你所見,在jinja2中,使用set關鍵字定義變數,執行如下ad-hoc命令渲染模板
# ansible test70 -m template -a "src=test.j2 dest=/opt/test"
最終生成的檔案內容如下
# cat /opt/test jinja2 test abc
直接在模板中定義變數可以方便我們的測試。
剛才說完了"if",現在來聊聊"for",for迴圈的基本語法如下:
{% for 迭代變數 in 可迭代物件 %} {{ 迭代變數 }} {% endfor %}
沒錯,如你所見,與"if"很像,"for"需要使用"endfor"作為結束,jinja2的控制語句大多都會遵循這個規則,即"XXX"控制語句需要使用"endXXX"作為結尾,之後不再進行贅述。
先看一個簡單的for迴圈示例,如下
# cat test.j2 jinja2 test {% for i in [3,1,7,8,2] %} {{ i }} {% endfor %}
上例中我們直接在模板檔案的for迴圈中定義了一個列表,執行如下命令,對模板進行渲染
# ansible test70 -m template -a "src=test.j2 dest=/opt/test"
最終生成的檔案內容如下:
# cat /opt/test jinja2 test 3 1 7 8 2
從生成的內容可以看出,每次迴圈後都會自動換行,如果不想要換行,則可以使用如下語法
# cat test.j2 jinja2 test {% for i in [3,1,7,8,2] -%} {{ i }} {%- endfor %}
如你所見
我在for的結束控制符"%}"之前添加了減號"-"
在endfor的開始控制符"{%"之後新增到了減號"-"
渲染上述模板,最終的生成效果如下:
# cat test jinja2 test 31782
如上所示,列表中的每一項都沒有換行,而是連在了一起顯示,如果你覺得這樣顯示有些"擁擠",則可以稍微改進一下上述模板,如下:
jinja2 test {% for i in [3,1,7,8,2] -%} {{ i }}{{ ' ' }} {%- endfor %}
如上例所示,我們在迴圈每一項時,在每一項後面加入了一個空格字串,所以,最終生成的效果如下:
# cat test jinja2 test 3 1 7 8 2
其實,還有更加簡潔的寫法,就是將上述模板內容修改為如下內容:
# cat test.j2 jinja2 test {% for i in [3,1,7,8,2] -%} {{ i~' ' }} {%- endfor %}
如上例所示,我們直接在迭代變數的後面使用了波浪符"~",並且用波浪符將迭代變數和空格字串連在一起,渲染上述模板內容,最終生成內容的效果與剛才示例中的效果是相同的,在jinja2中,波浪符"~"就是字串連線符,它會把所有的運算元轉換為字串,並且連線它們。
"for"除了能夠迴圈操作列表,也能夠迴圈操作字典,示例如下:
# cat test.j2 jinja2 test {% for key,val in {'name':'bob','age':18}.iteritems() %} {{ key ~ ':' ~ val }} {% endfor %}
如上所示,在迴圈操作字典時,先使用iteritems函式對字典進行處理,然後使用key和val兩個變數作為迭代變數,分別用於存放字典中鍵值對的"鍵"和"值",所以,直接輸出兩個變數的值即可,key和val是我隨意起的變數名,你可以自己定義這兩個迭代變數的名稱,而且,上例中的iteritems函式也可以替換成items函式,但是推薦使用iteritems函式,上例最終生成內容如下:
# cat test jinja2 test age:18 name:bob
在使用for迴圈時,有一些內建的特殊變數可以使用,比如,如果我想要知道當前迴圈操作為整個迴圈的第幾次操作,則可以藉助"loop.index"特殊變數,示例如下:
# cat test.j2 jinja2 test {% for i in [3,1,7,8,2] %} {{ i ~ '----' ~ loop.index }} {% endfor %}
最終生成檔案內容如下:
# cat test jinja2 test 3----1 1----2 7----3 8----4 2----5
除了內建特殊變數"loop.index",還有一些其他的內建變數,它們的作用如下(此處先簡單的進行介紹,之後會給出示例):
loop.index 當前迴圈操作為整個迴圈的第幾次迴圈,序號從1開始 loop.index0 當前迴圈操作為整個迴圈的第幾次迴圈,序號從0開始 loop.revindex 當前迴圈操作距離整個迴圈結束還有幾次,序號到1結束 loop.revindex0 當前迴圈操作距離整個迴圈結束還有幾次,序號到0結束 loop.first 當操作可迭代物件中的第一個元素時,此變數的值為true loop.last 當操作可迭代物件中的最後一個元素時,此變數的值為true loop.length 可迭代物件的長度 loop.depth 當使用遞迴的迴圈時,當前迭代所在的遞迴中的層級,層級序號從1開始 loop.depth0 當使用遞迴的迴圈時,當前迭代所在的遞迴中的層級,層級序號從0開始 loop.cycle() 這是一個輔助函式,通過這個函式我們可以在指定的一些值中進行輪詢取值,具體參考之後的示例
注:我當前使用的ansible版本為2.7.0,此版本的ansible對應的jinja2模板引擎的版本為2.7.2,上述內建變數為jinja2的2.7.2版本中的內建變數,目前,較新的jinja2版本為2.10,在2.10版的jinja2中還可以使用loop.previtem、loop.nextitem等特殊內建變數。
如果你只是想單純的對一段內容迴圈的生成指定的次數,則可以藉助range函式完成,比如,迴圈3次
{% for i in range(3) %} something ... {% endfor %}
當然,range函式可以指定起始數字、結束數字、步長等,預設的起始數字為0,
{% for i in range(1,4,2) %} {{i}} {% endfor %}
上例表示從1開始,到4結束(不包括4),步長為2,也就是說只有1和3會輸出。
預設情況下,模板中的for迴圈不能像其他語言中的 for迴圈那樣使用break或者continue跳出迴圈,但是你可以在"for"迴圈中新增"if"過濾條件,以便符合條件時,迴圈才執行真正的操作,示例如下:
{% for i in [7,1,5,3,9] if i > 3 %} {{ i }} {% endfor %}
上述 示例表示只有列表中的數字大於3時,才輸出列表中的元素,剛才在介紹if表示式時,我們說過,if表示式可以和其他控制語句結合使用,就是這個意思,上例的語法就是 "if內聯表示式" 和 "for迴圈控制結構" 結合在一起的使用方式。
你可能會問,我們在for迴圈中使用if判斷控制語句進行判斷不是也可以實現上述語法的效果嗎?比如,使用如下示例的寫法。
{% for i in [7,1,5,3,9] %} {% if i>3 %} {{ i }} {%endif%} {% endfor %}
沒錯,如果僅僅是為了根據條件進行過濾,上述兩種寫法並沒有什麼不同,但是,如果你需要在迴圈中使用到loop.index這種計數變數時,兩種寫法則會有所區別,具體區別渲染如下模板內容後則會很明顯的看出來:
{% for i in [7,1,5,3,9] if i>3 %} {{ i ~'----'~ loop.index }} {% endfor %} {% for i in [7,1,5,3,9] %} {% if i>3 %} {{ i ~'----'~ loop.index}} {% endif %} {% endfor %}
最終生成的內容如下
# cat test 7----1 5----2 9----3 7----1 5----3 9----5
從上述結果可以看出,當使用if內聯表示式時,如果不滿足對應條件,則不會進入當次迭代,所以loop.index也不會進行計算,而當使用if控制語句進行判斷時,其實已經進入了當次迭代,loop.index也已經進行了計算。
當for迴圈中使用了if內聯表示式時,還可以與else控制語句結合使用,示例如下:
{% for i in [7,1,5,3,9] if i>10 %} {{ i }} {%else%} no one is greater than 10 {% endfor %}
如上例所示,for迴圈中存在if內聯表示式,if對應的條件為i > 10,即元素的值必須大於10,才回執行一次迭代操作,而for迴圈中還有一個else控制語句,else控制語句之後也有一行文字,那麼上例是什麼意思呢?上述示例表示,如果列表中的元素大於10,則進入當次迭代,輸出"i"的值,if對應的條件成立時,else塊後的內容不執行,如果列表中沒有任何一個元素大於10,即任何一個元素都不滿足條件,則渲染else塊後面的內容。
其實,當for迴圈中沒有使用if內聯表示式時,也可以使用else塊,示例如下
{% for u in userlist %} {{ u.name }} {%else%} no one {% endfor %}
上例中,只有userlist列表為空時,才會渲染else塊後的內容。
所以,綜上所述,如果因序列為空或者有條件過濾了序列中的所有專案而沒有執行迴圈時,你可以使用else渲染一個用於替換的塊。
for迴圈也支援遞迴操作,遞迴示例如下:
{% set dictionary={ 'name':'bob','son':{ 'name':'tom','son':{ 'name':'jerry' } } } %} {% for key,value in dictionary.iteritems() recursive %} {% if key == 'name' %} {% set fathername=value %} {% endif %} {% if key == 'son' %} {{ fathername ~"'s son is "~ value.name}} {{ loop( value.iteritems() ) }} {% endif %} {% endfor %}
如上例所示,我們定義了一個字典變數,從字典中可以看出,bob的兒子是tom,tom的兒子是jerry,然後我們使用for迴圈操作了這個字典,如前文所示,我們在操作字典時,使用了iteritems函式,在for迴圈的末尾,我們添加了recursive 修飾符,當for迴圈中有recursive時,表示這個迴圈是一個遞迴的迴圈,當我們需要在for迴圈中進行遞迴時,只要在需要進行遞迴的地方呼叫loop函式即可,沒錯,如你所見,上例中的"loop( value.iteritems() )"即為呼叫遞迴的部分,由於value也是一個字典,所以需要使用iteritems函式進行處理。
渲染上述模板內容,最終效果如下
bob's son is tom tom's son is jerry
上文中總結的loop.depth變數和loop.depth0變數此處就不進行示例了,你可以自己動手寫一個遞迴實驗一下。
剛才在總結與迴圈有關的內建變數時,還提到了一個輔助函式,它就是"loop.cycle()",它能夠讓我們在指定的一些值中進行輪詢取值,這樣說可能不夠直觀,不如來看一個小示例,如下:
{% set userlist=['Naruto','Kakashi','Sasuke','Sakura','Lee','Gaara','Itachi'] %} {% for u in userlist %} {{ u ~'----'~ loop.cycle('team1','team2','team3')}} {%endfor%}
上例中,我們定義了一個使用者列表,這個列表裡面有一些人,現在,我想要將這些人分組,按照順序將這些人分別分配到三個組中,直到分完為止,三個組的組名分別為team1、team2、team3,渲染上例的內容,最終生成內容如下:
Naruto----team1 Kakashi----team2 Sasuke----team3 Sakura----team1 Lee----team2 Gaara----team3 Itachi----team1
從生成的內容可以看出,使用者與三個組已經輪詢的進行了結合。
剛才我們提到過,預設情況下,模板中的for迴圈無法使用break和continue,不過jinja2支援一些擴充套件,如果我們在ansible中啟用這些擴充套件,則可以讓模板中的for迴圈支援break和continue,方法如下:
如果想要開啟對應的擴充套件支援,需要修改ansible的配置檔案/etc/ansible/ansible.cfg,預設情況下未啟用jinja2的擴充套件,如果想要啟用jinja2擴充套件,則需要設定jinja2_extension選項,這個設定項預設情況下是註釋的,我的預設設定如下
#jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n
把註釋符去掉,預設已經有兩個擴充套件了,如果想要支援break和continue,則需要新增一個loopcontrols擴充套件,最終配置如下
jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n,jinja2.ext.loopcontrols
完成上述配置步驟即可在for迴圈中使用break和continue控制語句,與其他語言一樣,break表示結束整個迴圈,continue表示結束當次迴圈,示例如下:
{% for i in [7,1,5,3,9] %} {% if loop.index is even %} {%continue%} {%endif%} {{ i ~'----'~ loop.index }} {% endfor %}
上述示例表示偶數次的迴圈將會跳過,even這個tests在前文中已經總結過,此處不再贅述。
break的示例如下:
{% for i in [7,1,5,3,9] %} {% if loop.index > 3 %} {%break%} {%endif%} {{i ~'---'~ loop.index}} {% endfor %}
上例表示3次迭代以後的元素不會被處理
如果我們想要在jinja2中修改列表中的內容,則需要藉助jinja2的另一個擴充套件,這個擴充套件的名字就是"do",細心如你肯定已經發現了,剛才修改jinja2_extensions配置的時候,預設就有這個擴充套件,它的名字是jinja2.ext.do,通過do擴充套件修改列表的示例如下:
{% set testlist=[3,5] %} {% for i in testlist %} {{i}} {% endfor %} {%do testlist.append(7)%} {% for i in testlist %} {{i}} {% endfor %}
如上例所示,我們先定義了一個列表,然後遍歷了這個列表,使用 do在列表的末尾添加了一個元素,數字7,然後又遍歷 了它。
這篇文章就先總結到這裡,希望能夠對你有所幫助~
我的微信公眾號
關注"實用運維筆記"微信公眾號,當部落格中有新文章時,可第一時間得知哦~