黑帽联盟

 找回密码
 会员注册
查看: 1184|回复: 0
打印 上一主题 下一主题

[基础服务] ansible笔记(39):jinja2模板(二)

[复制链接]
yun 黑帽联盟官方人员 

920

主题

37

听众

1364

积分

超级版主

Rank: 8Rank: 8

  • TA的每日心情
    奋斗
    2019-10-18 11:20
  • 签到天数: 678 天

    [LV.9]以坛为家II

    本帖最后由 yun 于 2019-9-11 17:17 编辑

    ansible是一个系列文章,我们会尽量以通俗易懂的方式总结ansible的相关知识点。
    ansible系列博文直达链接:ansible轻松入门系列
    "ansible系列"中的每篇文章都建立在前文的基础之上,所以,请按照顺序阅读这些文章,否则有可能在阅读中遇到障碍。

    前一篇文章中我们提到过,在jinja2中,使用"{%  %}"对控制语句进行包含,比如"if"控制语句、"for"循环控制语句等 都需要包含在"{%  %}"中,那么这篇文章我们就来聊聊"{%  %}"。

    先来聊聊if,与其他语言相同,if用来进行条件判断,在jinja2中,if的语法如下:
    1. {% if 条件 %}
    2. ...
    3. ...
    4. ...
    5. {% endif %}
    复制代码
    如你所见,当使用if语法时,需要使用endif作为结束,示例模板如下:
    1. # cat test.j2
    2. jinja2 test

    3. {% if testnum > 3 %}
    4. greater than 3
    5. {% endif %}
    复制代码
    使用如下playbook对模板文件进行渲染
    注:不要使用ad-hoc命令调用template模块进行渲染,因为使用命令调用template模块时,无论你传入的数据是哪种类型,都会被当做字符串进行处理,所以此处使用playbook渲染模板,以保证数据类型的正确。
    1. # cat temptest.yml
    2. ---
    3. - hosts: test70
    4.   remote_user: root
    5.   gather_facts: no
    6.   tasks:
    7.   - template:
    8.       src: /testdir/ansible/test.j2
    9.       dest: /opt/test
    10.     vars:
    11.       testnum: 5
    复制代码
    最终生成的文件内容如下:
    1. # cat /opt/test
    2. jinja2 test

    3. greater than 3
    复制代码
    很简单吧,使用if进行条件判断,能够让我们的模板变得更加灵活。
    除了"if"结构,当然还有"if...else..."结构,语法如下:
    1. {% if 条件 %}
    2. ...
    3. {% else %}
    4. ...
    5. {% endif %}
    复制代码
    与其他语言一样,也有"if...else if..."的语法结构,如下:
    1. {% if 条件一 %}
    2. ...
    3. {% elif 条件二 %}
    4. ...
    5. {% elif 条件N %}
    6. ...
    7. {% endif %}
    复制代码
    或者结合在一起使用,语法如下
    1. {% if 条件一 %}
    2. ...
    3. {% elif 条件N %}
    4. ...
    5. {% else %}
    6. ...
    7. {% endif %}
    复制代码
    上述语法就是"if"控制语句的语法。

    其实,说到"if",不仅仅有"if"控制语句,还有"if"表达式,利用"if"表达式,可以实现类似三元运算的效果,示例模板内容如下:
    1. # cat test.j2
    2. jinja2 test
    3. {{ 'a' if 2>1 else 'b' }}
    复制代码
    渲染后的文件内容如下
    1. # cat /opt/test
    2. jinja2 test
    3. a
    复制代码
    如果你使用过其他语言的三目运算,或者你使用过python的三元运算,那么你一定已经看明白了上述表达式的含义,上例中的if表达式的含义为,如果2>1这个条件为真,则使用'a',如果2>1这个条件不成立,则使用'b',而2必定大于1,所以条件成立,最终使用'a',在jinja2中,if表达式的语法如下:
    1. <do something> if <something is true> else <do something else>
    复制代码
    "if"表达式和"if"控制语句并不是一个东西,"if"表达式可以与其他的控制语句结合使用,我们后面再聊

    在前文的示例中,我们都是在playbook中定义变量,然后在模板文件中使用变量,其实,我们也可以直接在模板文件中定义变量,示例如下:
    1. # cat test.j2
    2. jinja2 test
    3. {% set teststr='abc' %}
    4. {{ teststr }}
    复制代码
    如你所见,在jinja2中,使用set关键字定义变量,执行如下ad-hoc命令渲染模板
    1. # ansible test70 -m template -a "src=test.j2 dest=/opt/test"
    复制代码
    最终生成的文件内容如下
    1. # cat /opt/test
    2. jinja2 test
    3. abc
    复制代码
    直接在模板中定义变量可以方便我们的测试。

    刚才说完了"if",现在来聊聊"for",for循环的基本语法如下:
    1. {% for 迭代变量 in 可迭代对象 %}
    2. {{ 迭代变量 }}
    3. {% endfor %}
    复制代码
    没错,如你所见,与"if"很像,"for"需要使用"endfor"作为结束,jinja2的控制语句大多都会遵循这个规则,即"XXX"控制语句需要使用"endXXX"作为结尾,之后不再进行赘述。
    先看一个简单的for循环示例,如下
    1. # cat test.j2
    2. jinja2 test
    3. {% for i in [3,1,7,8,2] %}
    4. {{ i }}
    5. {% endfor %}
    复制代码
    上例中我们直接在模板文件的for循环中定义了一个列表,执行如下命令,对模板进行渲染
    1. # ansible test70 -m template -a "src=test.j2 dest=/opt/test"
    复制代码
    最终生成的文件内容如下:
    1. # cat /opt/test
    2. jinja2 test
    3. 3
    4. 1
    5. 7
    6. 8
    7. 2
    复制代码
    从生成的内容可以看出,每次循环后都会自动换行,如果不想要换行,则可以使用如下语法
    1. # cat test.j2
    2. jinja2 test
    3. {% for i in [3,1,7,8,2] -%}
    4. {{ i }}
    5. {%- endfor %}
    复制代码
    如你所见
    我在for的结束控制符"%}"之前添加了减号"-"
    在endfor的开始控制符"{%"之后添加到了减号"-"
    渲染上述模板,最终的生成效果如下:
    1. # cat test
    2. jinja2 test
    3. 31782
    复制代码
    如上所示,列表中的每一项都没有换行,而是连在了一起显示,如果你觉得这样显示有些"拥挤",则可以稍微改进一下上述模板,如下:
    1. jinja2 test
    2. {% for i in [3,1,7,8,2] -%}
    3. {{ i }}{{ ' ' }}
    4. {%- endfor %}
    复制代码
    如上例所示,我们在循环每一项时,在每一项后面加入了一个空格字符串,所以,最终生成的效果如下:
    1. # cat test
    2. jinja2 test
    3. 3 1 7 8 2
    复制代码
    其实,还有更加简洁的写法,就是将上述模板内容修改为如下内容:
    1. # cat test.j2
    2. jinja2 test
    3. {% for i in [3,1,7,8,2] -%}
    4. {{ i~' ' }}
    5. {%- endfor %}
    复制代码
    如上例所示,我们直接在迭代变量的后面使用了波浪符"~",并且用波浪符将迭代变量和空格字符串连在一起,渲染上述模板内容,最终生成内容的效果与刚才示例中的效果是相同的,在jinja2中,波浪符"~"就是字符串连接符,它会把所有的操作数转换为字符串,并且连接它们。

    "for"除了能够循环操作列表,也能够循环操作字典,示例如下:
    1. # cat test.j2
    2. jinja2 test
    3. {% for key,val in {'name':'bob','age':18}.iteritems() %}
    4. {{ key ~ ':' ~ val }}
    5. {% endfor %}
    复制代码
    如上所示,在循环操作字典时,先使用iteritems函数对字典进行处理,然后使用key和val两个变量作为迭代变量,分别用于存放字典中键值对的"键"和"值",所以,直接输出两个变量的值即可,key和val是我随意起的变量名,你可以自己定义这两个迭代变量的名称,而且,上例中的iteritems函数也可以替换成items函数,但是推荐使用iteritems函数,上例最终生成内容如下:
    1. # cat test
    2. jinja2 test
    3. age:18
    4. name:bob
    复制代码
    在使用for循环时,有一些内置的特殊变量可以使用,比如,如果我想要知道当前循环操作为整个循环的第几次操作,则可以借助"loop.index"特殊变量,示例如下:
    1. # cat test.j2
    2. jinja2 test
    3. {% for i in [3,1,7,8,2] %}
    4. {{ i ~ '----' ~ loop.index }}
    5. {% endfor %}
    复制代码
    最终生成文件内容如下:
    1. # cat test
    2. jinja2 test
    3. 3----1
    4. 1----2
    5. 7----3
    6. 8----4
    7. 2----5
    复制代码
    除了内置特殊变量"loop.index",还有一些其他的内置变量,它们的作用如下(此处先简单的进行介绍,之后会给出示例):
    1. loop.index   当前循环操作为整个循环的第几次循环,序号从1开始
    2. loop.index0   当前循环操作为整个循环的第几次循环,序号从0开始
    3. loop.revindex  当前循环操作距离整个循环结束还有几次,序号到1结束
    4. loop.revindex0 当前循环操作距离整个循环结束还有几次,序号到0结束
    5. loop.first    当操作可迭代对象中的第一个元素时,此变量的值为true
    6. loop.last    当操作可迭代对象中的最后一个元素时,此变量的值为true
    7. loop.length   可迭代对象的长度
    8. loop.depth   当使用递归的循环时,当前迭代所在的递归中的层级,层级序号从1开始
    9. loop.depth0   当使用递归的循环时,当前迭代所在的递归中的层级,层级序号从0开始
    10. 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次
    1. {% for i in range(3) %}
    2. something
    3. ...
    4. {% endfor %}
    复制代码
    当然,range函数可以指定起始数字、结束数字、步长等,默认的起始数字为0,
    1. {% for i in range(1,4,2) %}
    2.   {{i}}
    3. {% endfor %}
    复制代码
    上例表示从1开始,到4结束(不包括4),步长为2,也就是说只有1和3会输出。

    默认情况下,模板中的for循环不能像其他语言中的 for循环那样使用break或者continue跳出循环,但是你可以在"for"循环中添加"if"过滤条件,以便符合条件时,循环才执行真正的操作,示例如下:
    1. {% for i in [7,1,5,3,9] if i > 3 %}
    2.   {{ i }}
    3. {% endfor %}
    复制代码
    上述 示例表示只有列表中的数字大于3时,才输出列表中的元素,刚才在介绍if表达式时,我们说过,if表达式可以和其他控制语句结合使用,就是这个意思,上例的语法就是 "if内联表达式" 和 "for循环控制结构" 结合在一起的使用方式。
    你可能会问,我们在for循环中使用if判断控制语句进行判断不是也可以实现上述语法的效果吗?比如,使用如下示例的写法。
    1. {% for i in [7,1,5,3,9] %}
    2.   {% if i>3 %}
    3.     {{ i }}
    4.   {%endif%}
    5. {% endfor %}
    复制代码
    没错,如果仅仅是为了根据条件进行过滤,上述两种写法并没有什么不同,但是,如果你需要在循环中使用到loop.index这种计数变量时,两种写法则会有所区别,具体区别渲染如下模板内容后则会很明显的看出来:
    1. {% for i in [7,1,5,3,9] if i>3 %}
    2. {{ i ~'----'~ loop.index }}
    3. {% endfor %}

    4. {% for i in [7,1,5,3,9] %}
    5. {% if i>3 %}
    6. {{ i ~'----'~ loop.index}}
    7. {% endif %}
    8. {% endfor %}
    复制代码
    最终生成的内容如下
    1. # cat test
    2. 7----1
    3. 5----2
    4. 9----3

    5. 7----1
    6. 5----3
    7. 9----5
    复制代码
    从上述结果可以看出,当使用if内联表达式时,如果不满足对应条件,则不会进入当次迭代,所以loop.index也不会进行计算,而当使用if控制语句进行判断时,其实已经进入了当次迭代,loop.index也已经进行了计算。

    当for循环中使用了if内联表达式时,还可以与else控制语句结合使用,示例如下:
    1. {% for i in [7,1,5,3,9] if i>10 %}
    2. {{ i }}
    3. {%else%}
    4. no one is greater than 10
    5. {% endfor %}
    复制代码
    如上例所示,for循环中存在if内联表达式,if对应的条件为i > 10,即元素的值必须大于10,才回执行一次迭代操作,而for循环中还有一个else控制语句,else控制语句之后也有一行文本,那么上例是什么意思呢?上述示例表示,如果列表中的元素大于10,则进入当次迭代,输出"i"的值,if对应的条件成立时,else块后的内容不执行,如果列表中没有任何一个元素大于10,即任何一个元素都不满足条件,则渲染else块后面的内容。

    其实,当for循环中没有使用if内联表达式时,也可以使用else块,示例如下
    1. {% for u in userlist %}
    2.   {{ u.name }}
    3. {%else%}
    4.   no one
    5. {% endfor %}
    复制代码
    上例中,只有userlist列表为空时,才会渲染else块后的内容。

    所以,综上所述,如果因序列为空或者有条件过滤了序列中的所有项目而没有执行循环时,你可以使用else渲染一个用于替换的块。

    for循环也支持递归操作,递归示例如下:
    1. {% set dictionary={ 'name':'bob','son':{ 'name':'tom','son':{ 'name':'jerry' } } }  %}

    2. {% for key,value in dictionary.iteritems() recursive %}
    3.   {% if key == 'name' %}
    4.     {% set fathername=value %}
    5.   {% endif %}

    6.   {% if key == 'son' %}
    7.     {{ fathername ~"'s son is "~ value.name}}
    8.     {{ loop( value.iteritems() ) }}
    9.   {% endif %}
    10. {% endfor %}
    复制代码
    如上例所示,我们定义了一个字典变量,从字典中可以看出,bob的儿子是tom,tom的儿子是jerry,然后我们使用for循环操作了这个字典,如前文所示,我们在操作字典时,使用了iteritems函数,在for循环的末尾,我们添加了recursive 修饰符,当for循环中有recursive时,表示这个循环是一个递归的循环,当我们需要在for循环中进行递归时,只要在需要进行递归的地方调用loop函数即可,没错,如你所见,上例中的"loop( value.iteritems() )"即为调用递归的部分,由于value也是一个字典,所以需要使用iteritems函数进行处理。

    渲染上述模板内容,最终效果如下
    1.       bob's son is tom
    2.            
    3.       tom's son is jerry
    复制代码
    上文中总结的loop.depth变量和loop.depth0变量此处就不进行示例了,你可以自己动手写一个递归实验一下。

    刚才在总结与循环有关的内置变量时,还提到了一个辅助函数,它就是"loop.cycle()",它能够让我们在指定的一些值中进行轮询取值,这样说可能不够直观,不如来看一个小示例,如下:
    1. {% set userlist=['Naruto','Kakashi','Sasuke','Sakura','Lee','Gaara','Itachi']  %}

    2. {% for u in userlist %}
    3. {{ u ~'----'~ loop.cycle('team1','team2','team3')}}
    4. {%endfor%}
    复制代码
    上例中,我们定义了一个用户列表,这个列表里面有一些人,现在,我想要将这些人分组,按照顺序将这些人轮流分配到三个组中,直到分完为止,三个组的组名分别为team1、team2、team3,渲染上例的内容,最终生成内容如下:
    1. Naruto----team1
    2. Kakashi----team2
    3. Sasuke----team3
    4. Sakura----team1
    5. Lee----team2
    6. Gaara----team3
    7. Itachi----team1
    复制代码
    从生成的内容可以看出,用户与三个组已经轮询的进行了结合。

    刚才我们提到过,默认情况下,模板中的for循环无法使用break和continue,不过jinja2支持一些扩展,如果我们在ansible中启用这些扩展,则可以让模板中的for循环支持break和continue,方法如下:
    如果想要开启对应的扩展支持,需要修改ansible的配置文件/etc/ansible/ansible.cfg,默认情况下未启用jinja2的扩展,如果想要启用jinja2扩展,则需要设置jinja2_extension选项,这个设置项默认情况下是注释的,我的默认设置如下
    1. #jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n
    复制代码
    把注释符去掉,默认已经有两个扩展了,如果想要支持break和continue,则需要添加一个loopcontrols扩展,最终配置如下
    1. jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n,jinja2.ext.loopcontrols
    复制代码
    完成上述配置步骤即可在for循环中使用break和continue控制语句,与其他语言一样,break表示结束整个循环,continue表示结束当次循环,示例如下:
    1. {% for i in [7,1,5,3,9] %}
    2.   {% if loop.index is even %}
    3.     {%continue%}
    4.   {%endif%}
    5.   {{ i ~'----'~ loop.index }}
    6. {% endfor %}
    复制代码
    上述示例表示偶数次的循环将会跳过,even这个tests在前文中已经总结过,此处不再赘述。
    break的示例如下:
    1. {% for i in [7,1,5,3,9] %}
    2.   {% if loop.index > 3 %}
    3.     {%break%}
    4.   {%endif%}
    5.   {{i ~'---'~ loop.index}}
    6. {% endfor %}
    复制代码
    上例表示3次迭代以后的元素不会被处理

    如果我们想要在jinja2中修改列表中的内容,则需要借助jinja2的另一个扩展,这个扩展的名字就是"do",细心如你肯定已经发现了,刚才修改jinja2_extensions配置的时候,默认就有这个扩展,它的名字是jinja2.ext.do,通过do扩展修改列表的示例如下:
    1. {% set testlist=[3,5] %}

    2. {% for i in testlist  %}
    3.   {{i}}
    4. {% endfor %}

    5. {%do testlist.append(7)%}

    6. {% for i in testlist  %}
    7.   {{i}}
    8. {% endfor %}
    复制代码
    如上例所示,我们先定义了一个列表,然后遍历了这个列表,使用 do在列表的末尾添加了一个元素,数字7,然后又遍历 了它。

    这篇文章就先总结到这里,希望能够对你有所帮助~

    帖子永久地址: 

    黑帽联盟 - 论坛版权1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
    2、本站所有主题由该帖子作者发表,该帖子作者与黑帽联盟享有帖子相关版权
    3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和黑帽联盟的同意
    4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
    5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
    6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意
    7、黑帽联盟管理员和版主有权不事先通知发贴者而删除本文

    您需要登录后才可以回帖 登录 | 会员注册

    发布主题 !fastreply! 收藏帖子 返回列表 搜索
    回顶部