ansible筆記(40):jinja2模板(三)
- A+
所屬分類:ansible ofollow,noindex" target="_blank">運維技術
在本部落格中,ansible是一個系列文章,我們會盡量以通俗易懂的方式總結ansible的相關知識點。
ansible系列博文直達連結:ansible輕鬆入門系列
"ansible系列"中的每篇文章都建立在前文的基礎之上,所以, 請按照順序閱讀這些文章,否則有可能在閱讀中遇到障礙。
轉義的一些操作
前文中我們已經總結了jinja2模板的一些基礎用法,比如,變數和表示式被包含在"{{ }}"中,控制語句被包含在"{% %}", 註釋被包含在"{# #}"中,也就是說,在模板檔案中,一旦遇到"{{ }}"、"{% %}"或者"{# #}",jinja2模板引擎就會進行相應的處理,最終生成的檔案內容中並不會包含"{{ }}"或者"{% %}"這些字元,但是如果,最終生成的檔案中就是需要有"{{ }}"這一類的字元,該怎麼辦呢?有如下幾種方法。
最簡單的方法就是直接在"{{ }}"中使用引號將這類符號引起,當做純粹的字串進行處理,示例模板內容如下:
# cat test.j2 {{ '{{' }} {{ '}}' }} {{ '{{ test string }}' }} {{ '{% test string %}' }} {{ '{# test string #}' }}
上述模板最終生成的內容如下:
# cat test {{ }} {{ test string }} {% test string %} {# test string #}
但是如果有較多這樣的符號都需要保持原樣(不被jinja2解析),那麼使用上述方法可能比較麻煩,因為行多量大,大段的符號需要注意就比較煩人,所以,如果有較大的段落時,可以藉助"{% raw %}"塊,來實現剛才的需求,示例如下:
# cat test.j2 {% raw %} {{ test }} {% test %} {# test #} {% if %} {% for %} {% endraw %}
如上例所示,"{% raw %}"與"{% endraw %}"之間的所有"{{ }}"、"{% %}"或者"{# #}"都不會被jinja2解析,上例模板被渲染後,raw塊中的符號都會保持原樣,最終生成內容如下:
# cat test {{ test }} {% test %} {# test #} {% if %} {% for %}
預設情況下,變數和表示式被包含在"{{ }}"中,控制語句被包含在"{% %}",其實,我們也可以在呼叫模板引擎時,手動的指定一些符號,這些符號可以替換預設的"{{ }}"和"{% %}",當我們在ansible中呼叫templdate模組時,可以使用variable_start_string引數指定一個符號,這個符號用於替換"{{ }}"中的"{{",同時,可以使用variable_end_string引數指定一個符號,這個符號用於替換"{{ }}"中的"}}",示例如下:
# cat test.j2 {% set test='abc' %} (( test )) {{ test }} {{ test1 }} {{ 'test' }} {{ 'test1' }}
上述內容為示例模板的內容,當我們呼叫templdate模組時,執行如下命令,注意,如下命令表示使用"(("代替"{{",使用"))"代替"}}"。
# ansible test70 -m template -a "src=test.j2 dest=/opt/test variable_start_string='((' variable_end_string='))'"
執行上述命令後,最終生成的檔案內容如下:
# cat test abc {{ test }} {{ test1 }} {{ 'test' }} {{ 'test1' }}
如你所見,當我們使用了指定的符號替換了預設的"{{ }}"之後,"{{ }}"字元將會保持原樣,"{{ }}"中的內容也不會再被jinja2模板引擎解析,我們指定的符號為"(( ))",所以,jinja2引擎在遇到"(( ))"符號時,才會進行對應的解析渲染。
同理,我們可以使用block_start_string引數指定一個符號,這個符號用於替換"{% %}"中的"{% "
可以使用block_end_string引數指定一個符號,這個符號用於替換"{% %}"中的"%}"
剛才提到的這幾個引數在2.4以及之後版本的ansible中可用。
如果你想要生成的檔案通篇都是"{{ }}"和"{% %}"這種符號,只有個別地方需要用到模板引擎的表示式和控制語句,那麼不如直接使用剛才提到的引數,使用指定的符號將預設的符號替代,替代後,"{{ }}"或"{% %}"都會保持原樣,不會被解析。
巨集相關總結
jinja2中也有類似函式的東西,它叫做"巨集",利用巨集,我們可以方便快捷的重複的利用一段內容,並且把這段內容當做一個獨立的邏輯單元,與其他語言中的函式一樣,jinja2的巨集也可以傳入引數,先來看一個最簡單的巨集,示例模板如下
# cat test.j2 {% macro testfunc() %} test string {% endmacro %} {{ testfunc() }}
如上例所示,定義巨集時需要使用"{% macro %}"開頭,使用"{% endmacro %}"結束,上例中,我定義了一個名為testfunc的巨集,與大多數語言中的函式一樣,巨集的括號中可以傳入引數,但是上例的巨集中並沒有定義任何引數,testfunc中的內容沒有什麼意義,只是用於示例巨集的用法,"{% macro %}"和"{% endmacro %}"之間的內容只是用來定義巨集,如果想要真正的使用巨集,還需要引用它,上例的最後一行就是在呼叫testfunc巨集。
看完最簡單的巨集,再來看一個傳參的巨集,示例如下:
# cat test.j2 {% set testvar1='teststr1' %} {% set testvar2=2 %} {% macro testfunc(tv1,tv2) %} test string {{tv1}} {{tv2}} {% endmacro %} {{ testfunc(testvar1,testvar2) }}
如上例所示,我們在定義巨集時,定義了兩個引數,然後 在呼叫巨集時,傳入了提前定義好的testvar1變數和testvar2變數,但是上述示例有一個很明顯的問題,就是如果巨集在定義的時候有對應的引數,在呼叫巨集時就必須傳入對應的引數,否則就會報錯,其實,我們還可以在定義巨集時,為對應的引數指定一個預設值,當在呼叫巨集時沒有顯式的指定對應的引數時,巨集就使用引數的預設值,示例模板如下:
{% macro testfunc(tv1=111) %} test string {{tv1}} {% endmacro %} {{ testfunc( ) }} {{ testfunc(666) }}
渲染結果如下:
test string 111 test string 666
如上例所示,我們在定義巨集時,為tv1引數定義了預設值111,然後呼叫了兩次testfunc巨集,第一次沒有傳入對應引數,使用了預設值,第二次呼叫巨集時傳入了對應引數,於是使用了傳入的值
注意,當有多個引數時,如果並不是所有引數都有預設值,那麼沒有預設值的引數必須在有預設值的引數之前,否則會出現錯誤,這一點與python中的函式傳參時的注意點是一樣的,下例中對錯誤的用法進行了示例。
下例是一個錯誤示例,下例的巨集中定義了三個引數,tv1和tv3有預設值,但是tv2沒有,這樣就是錯誤的,因為tv2引數沒有設定預設值,但是它的前面卻有一個定義了預設值的引數,如下模板雖然能夠渲染成功,但是得到的結果卻是錯誤的
{% macro testfunc(tv1=1,tv2,tv3=3) %} test string {{tv1}} {{tv2}} {{tv3}} {% endmacro %} {{ testfunc( 'a' ) }}
上述錯誤示例渲染後的結果如下
test string a 1 3
上述結果與我們預期的正確結果並不相同,所以我們應該避免這種情況發生。
如下寫法是正確的,因為所有沒有預設值的引數在有預設值的引數之前
{% macro testfunc(tv1,tv2,tv3=3,tv4=4) %} test string {{tv1}} {{tv2}} {{tv3}} {{tv4}} {% endmacro %} {{ testfunc( 'aa','a' ) }}
渲染上述模板結果如下
test string aa a 3 4
如你所見,呼叫巨集時傳入的值會按照順序與沒有預設值的引數進行對應。
在傳入引數時,也可以顯式的指明引數的名稱,示例如下:
{% macro testfunc(tv1,tv2=2,tv3=3) %} test string {{tv1}} {{tv2}} {{tv3}} {% endmacro %} {{ testfunc( 111,tv3='ccc' ) }}
如上例所示,在呼叫巨集時,傳入了兩個引數 ,第一個引數與沒有預設值的tv1對應,第二個引數指明瞭引數名為tv3,表示設定tv3引數的值為'ccc',由於沒有傳入針對tv2的值,所以tv2將會使用對應的預設值,上例渲染後內容如下
test string 111 2 ccc
顯式的指定引數名可以幫助我們靈活的傳入引數。
其實,在巨集的內部,有三個預設的內建特殊變數可供我們使用,它們分別是varargs、kwargs、caller,它們的作用我們慢慢道來。
我們可以在呼叫巨集時,多傳入幾個額外的引數,這些額外的引數會作為一個元組儲存在varargs變數上,我們可以通過獲取varargs變數的值獲取到額外傳入的引數,示例如下:
cat test.j2 {% macro testfunc(testarg1=1,testarg2=2) %} test string {{testarg1}} {{testarg2}} {{varargs}} {% endmacro %} {{ testfunc('a','b','c','d','e') }}
上例中我們只定義了兩個引數,但是在呼叫巨集時,我們傳入了5個引數,而在巨集裡面,我們輸出了內建變數varargs,最終渲染後的效果如下:
test string a b ('c', 'd', 'e')
既然varargs變數裡面儲存了多餘的引數,那麼如果巨集壓根就沒有定義任何引數,我們卻傳入了一些引數,那麼這些所有傳入的引數都是“多餘”出的引數,也可以使用varargs變數處理這些引數,示例如下:
{% macro testfunc() %} test string {%for i in varargs%} {{i}} {%endfor%} {{ '--------' }} {% endmacro %} {{ testfunc() }} {{ testfunc(1,2,3) }}
如上例所示,定義巨集時,並沒有定義任何引數,但是在使用巨集時,可以傳入任意個數的引數,或者不傳任何引數,這種使用方式類似於python中的可變引數,上例的最終渲染效果如下:
test string -------- test string 1 2 3 --------
聊完內建變數varargs,再來聊聊變數kwargs,kwargs變數與varargs變數的作用很像,但是kwargs變數只是針對'關鍵字引數'而言的,而varargs變數是針對'非關鍵字引數'而言的,看了如下示例你就會明白了
{% macro testfunc(tv1='tv1') %} test string {{varargs}} {{kwargs}} {% endmacro %} {{ testfunc('a',2,'test',testkeyvar='abc') }}
如上例所示,我們在定義巨集時,定義了一個引數tv1,並且設定了預設值,在巨集中,我們輸出了varargs變數和kwargs變數,在呼叫巨集時,我們多傳入了3個引數,最後一個引數是一個帶有引數名的關鍵字引數,那麼渲染上述模板,內容如下
test string (2, 'test') {'testkeyvar': 'abc'}
如上所示,多餘的非關鍵字引數都會儲存在varargs變數中,varargs變數的結構是一個元組,而多餘的關鍵字引數都會儲存在kwargs變數中,kwargs變數的結構是一個字典,kwargs變數實現的效果與Python的關鍵字引數效果類似。
瞭解了varargs變數和kwargs變數,現在來說說caller變數。
與其說是caller變數,不如稱其為caller函式或者caller方法,caller可以幫助我們將巨集中的內容進行替換,這樣描述可能不太容易理解,不如先來看一個小例子:
{% macro testfunc() %} test string {{caller()}} {% endmacro %}
如上例所示,我們定義了一個testfunc巨集,在testfunc巨集中,"{{caller()}}"部分可以被"其他內容"替換,但是此刻,我們還沒有呼叫testfunc巨集,如果想要替換testfunc巨集中的"{{caller()}}"部分,則需要在呼叫testfunc巨集時,使用"call語句塊"進行呼叫,示例如下:
{% macro testfunc() %} test string {{caller()}} {% endmacro %} {%call testfunc()%} something~~~~~ something else~~~~~ {%endcall%}
如上例所示,我們使用了"{%call%}"語句塊呼叫了testfunc巨集,"{%call%}"和"{%endcall%}"之間的內容將會替換testfunc巨集中的"{{caller()}}"部分,上例模板最終渲染效果如下
test string something~~~~~ something else~~~~~
是不是覺得跟傳引數的效果一模一樣,就像傳了一個引數給testfunc巨集一樣,當我們要傳入大段內容或者複雜的內容時,可以藉助caller進行傳遞,當然,上例只是 為了讓你能夠更加直觀的瞭解caller的用法,caller其實還能夠幫助我們在一個巨集中呼叫另一個巨集,示例如下:
{% macro testfunc() %} test string {{caller()}} {% endmacro %} {% macro testfunc1() %} {% for i in range(3) %} {{i}} {% endfor %} {% endmacro %} {%call testfunc()%} {{testfunc1()}} {%endcall%}
如上例所示,我們定義了兩個巨集,testfunc和testfunc1,我們將testfunc1傳遞到了testfunc中。
其實,"caller()"也可以接收引數,只要在call塊中提前定義好,在caller中傳入引數即可,示例如下:
{% macro testfunc() %} test string {{caller('somethingElse~~')}} {% endmacro %} {%call(testvar) testfunc()%} something~~~~ {{testvar}} {%endcall%}
如上例所示,call塊中定義了testvar引數,call塊中的內容使用了testvar引數,在testfunc巨集中的呼叫caller時,傳入了一個字串作為引數,上例最終的渲染的效果如下:
test string something~~~~ somethingElse~~
當testfunc中的某些內容需要迴圈的進行替換時,這種方法非常有效。
除了varargs、kwargs、caller這些內部變數,巨集還有一些屬性可以使用。
可用的巨集屬性如下,此處先對這些屬性進行描述,之後會進行示例:
name屬性:巨集的名稱。
arguments屬性:巨集中定義的所有引數的引數名,這些引數名組成了一個元組存放在arguments中。
defaults屬性:巨集中定義的引數如果有預設值,這些預設值組成了一個元組存放在defaults中。
catch_varargs屬性:如果巨集中使用了varargs變數,此屬性的值為true。
catch_kwargs屬性: 如果巨集中使用了kwargs變數,此屬性的值為true。
caller屬性:如果巨集中使用了caller變數,此屬性值為true。
上述巨集屬性的使用示例如下,可以對比著渲染後的結果檢視:
# cat test.j2 {% macro testfunc(tv1,tv2,tv3=3,tv4=4) %} test string {{tv1}} {{tv2}} {{tv3}} {{tv4}} {% endmacro %} {{testfunc.name}} {{testfunc.arguments}} {{testfunc.defaults}} {{testfunc.catch_varargs}} {{testfunc.catch_kwargs}} {{testfunc.caller}} {{'################################'}} {% macro testfunc1(tv1='a',tv2='b') %} test string {{tv1}} {{tv2}} {{varargs}} {{kwargs}} {% endmacro %} {{testfunc1.catch_varargs}} {{testfunc1.catch_kwargs}} {{testfunc1.caller}} {{'################################'}} {% macro testfunc2() %} test string {{caller()}} {% endmacro %} {{testfunc2.caller}}
如你所見,我並沒有呼叫巨集,但是可以直接使用巨集的屬性,上例模板內容渲染後的結果如下:
testfunc ('tv1', 'tv2', 'tv3', 'tv4') (3, 4) False False False ################################ True True False ################################ True
這篇文章就先 總結道這裡,希望可以對你有所幫助~~
我的微信公眾號
關注"實用運維筆記"微信公眾號,當部落格中有新文章時,可第一時間得知哦~