标题:[实践OK]Ansible里的后向引用124进行替换,以及正则匹配shell下写在一行和写YAML文件的正则区别,结合正则猫RegexBuddy/Patterns的正确用法。PHP正则匹配反斜杠''和美元'$'的方法以及Ansible单行shell交互和写入yml文件的正则不同写法的原因和对比成功实践及理解。 出处:向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除 时间:Wed, 27 Jun 2018 11:23:41 +0000 作者:jackxiang 地址:http://jackxiang.com/post/9805/ 内容: 方便后面类似的需求,更快替换及测试,提高效率,如下: --- - name: easyswoole去掉PHP的 symlink和 stream_socket_server限制 gather_facts: no hosts: "{{ h | default('10.244.25.77') }}" tags: aixiu_web tasks: - name: easyswoole去掉PHP的 symlink和 stream_socket_server限制 lineinfile: dest: /usr/local/php/etc/php.ini regexp: '(.*)(symlink,)(.*)(stream_socket_server,)(.*)' line: '\1\3\5' backrefs: yes tags: getridofphpdisablefun ansible-playbook replace.yml -C -D 前置之1)Ansible的正则替换的模拟替换参数 -C -D,类似sed 的 -n 和 p结合只显示不真实替换,如下: ansible-playbook aixiu_web.yml -e h=10.244.5.108 -C -D -t addhttpcdnsrcip 前置之2)正则被多重括号包起来的一个顺序和内容界定相当重要,它是从左到右数的一个\1\2\3的反向引用实践备忘: regexp: '((.*)(\"agent\"\:\"\$http_user_agent\")(.*))' line: '\1--\2--\3--\4||' #line: '\1\n\2"http_cdn_src_ip":"$http_cdn_src_ip"\4' - '"agent":"$http_user_agent",' + '"agent":"$http_user_agent",'-- '--"agent":"$http_user_agent"--,'|| 上面1是最左边那个(,也就是所有的,第二个是匹配到的',它前面还有一些空行也被匹配上了的( '),所以上面显示有一些空的主要用来对新加的一行对齐, 第三个\3就是上一行去掉前和后的部分对于插入这行没有用(前无'后无,')(就是:"agent":"$http_user_agent"),第四个就是匹配到单独的一个(,'), 这个道理明白了也就对正则匹配出来的先后顺序有一个了解,再就是这个串特比有是\2里面的多个空格加一个单引号( '), 对于\n后面新加的一行对齐很重要,再就是\4也就是(,'),也是用来补充新加的一行少的部分,组成新一行起到了作用,见实践2: ') <=== \2 "http_cdn_src_ip":"$http_cdn_src_ip" <===新加的部分 ,' <=== \4 上面三行构成了一个完整的和上面一样有N个空格打头的字符串: '"http_cdn_src_ip":"$http_cdn_src_ip",' backrefs参数:默认情况下,当根据正则替换文本时,即使regexp参数中的正则存在分组,在line参数中也不能对正则中的分组进行引用,除非将backrefs参数的值设置为yes。backrefs=yes表示开启后向引用,这样,line参数中就能对regexp参数中的分组进行后向引用了,这样说不太容易明白,可以参考后面的示例命令理解。backrefs=yes除了能够开启后向引用功能,还有另一个作用,默认情况下,当使用正则表达式替换对应行时,如果正则没有匹配到任何的行,那么line对应的内容会被插入到文本的末尾,不过,如果使用了backrefs=yes,情况就不一样了,当使用正则表达式替换对应行时,同时设置了backrefs=yes,那么当正则没有匹配到任何的行时,则不会对文件进行任何操作,相当于保持原文件不变。 原文:https://blog.csdn.net/dylloveyou/article/details/80698531 实践1)一行命令实现了正则替换,也就是Shell->Ansible的一个路径,它于直接定到yml里的正则写法不一样,看实践2作比较,原因: 写到文件里和一行的正则写法是不一样的,写一行涉及到终端传Shell的问题,而写到yaml的Ansible文件里则是python的交互,所以正则写法不大一样,如下: shell交互: regexp='((.*)(\"agent\":\"\\\$http_user_agent\")(.*))' yaml交互: regexp: '((.*)(\"agent\"\:\"\$http_user_agent\")(.*))' 比对发现: 一)\: 冒号在shell不需要转义,而在yaml文件里需要转义。 二)而shell里对$转义的右斜杠需要再加两个右斜杠一共三次,而yaml文件里轩一次也就行了,后面有描述这个$的问题。 ansible 10.71.182.126 -m lineinfile -a "backrefs=yes dest=/usr/local/nginx/conf/nginx.conf regexp='((.*)(\"agent\":\"\\\$http_user_agent\")(.*))' line='\1\n\2\"http_cdn_src_ip\":\"\$http_cdn_src_ip\"\4'" 10.71.182.126 | CHANGED => { "backup": "", "changed": true, "msg": "line replaced" } 三)有条件的替换,以防止出现替换时因为多次运行相同的Ansible脚本进而多次插入,注意when里面的变量加上单引号为字符串,否则会出现判断不准的问题: - name: 配Web系统的Nginx虚拟主机及代码文件初始化配置文件软链接 gather_facts: no hosts: "{{ h | default('10.71.1*2.126') }}" tasks: - name: 检查http_cdn_src_ip是否存在 shell: cat /usr/local/nginx/conf/nginx.conf|grep http_cdn_src_ip|wc -l register: hostname_result ignore_errors: yes - debug: msg={{ hostname_result.stdout_lines[0] }} - name: 将CDN透传过来的客户端访问出口IP写入Nginx日志 lineinfile: dest: /usr/local/nginx/conf/nginx.conf regexp: '((.*)(\"agent\"\:\"\$http_user_agent\")(.*))' line: '\1\n\2"http_cdn_src_ip":"$http_cdn_src_ip"\4' backrefs: yes when: hostname_result.stdout_lines[0] == '0' 实践2)实践发现假如要写到Yaml文件里,上面单独这一行放Shell里运行可以,但是放到yaml文件里是不行的,怎么办,重新修改调试Ok的文件版本如下所示: --- - name: 配置服务器前后台Web系统的Nginx虚拟主机及代码文件初始化配置文件软链接 gather_facts: no hosts: "{{ h | default('10.244.25.77') }}" tags: aixiu_web tasks: - name: 将CDN透传过来的客户端访问出口IP写入Nginx日志 lineinfile: dest: /usr/local/nginx/conf/nginx.conf #regexp: '((.*)(\"agent\":\"\\\$http_user_agent\")(.*))' #line: '\1\n\2\"http_cdn_src_ip\":\"\$http_cdn_src_ip\"\4' regexp: '((.*)(\"agent\"\:\"\$http_user_agent\")(.*))' line: '\1\n\2"http_cdn_src_ip":"$http_cdn_src_ip"\4' backrefs: yes tags: addhttpcdnsrcip TASK [将CDN透传过来的客户端访问出口IP写入Nginx日志] ********************************************************************************************************** --- before: /usr/local/nginx/conf/nginx.conf (content) +++ after: /usr/local/nginx/conf/nginx.conf (content) @@ -66,6 +66,7 @@ '"xff":"$http_x_forwarded_for",' '"referer":"$http_referer",' '"agent":"$http_user_agent",' + '"http_cdn_src_ip":"$http_cdn_src_ip",' '"status":"$status"}'; access_log /data/logs/nginx/access.log main; #这一行显示冗余,并没有用,主要看+号。 changed: [10.244.25.77] 实践3)用ansible的insertafter实现: 如果用正则查到某行,在其后面写上也成用insertafter实现,但是这样据前面文章和实践就无法用这个\1\2这样的了,如果打开那个backrefs就需要regexp了,于是这样写实践是Ok的: - name: insert after lineinfile: dest: /usr/local/nginx/conf/nginx.conf insertafter: '((.*)(\"agent\"\:\"\$http_user_agent\")(.*))' line: " '\"http_cdn_src_ip\":\"$http_cdn_src_ip\",'" tags: insertafter --- before: /usr/local/nginx/conf/nginx.conf (content) +++ after: /usr/local/nginx/conf/nginx.conf (content) @@ -66,6 +66,7 @@ '"xff":"$http_x_forwarded_for",' '"referer":"$http_referer",' '"agent":"$http_user_agent",' + '"http_cdn_src_ip":"$http_cdn_src_ip",' '"status":"$status"}'; 实践4)进一步实践发现正则和insertafter混用也是可以的, 也就是说insertafter之后,再加一个regexp正则匹配出\1\2可用在line里,同时加上backrefs: yes,可行的, 如下实践也是能实现的,去掉之前的\1和\n即可,就在它后面插入即可,实践发现并没在后面插入,而是直接替换了,也就是说insertafter失效了,还得按实践2走才Ok,要不就老老实实的按实践3在后在插入,不要引入正则也成,引入正则就失去了insertafter的本来功能了: --- - name: insertafter with regexp gather_facts: no hosts: "{{ h | default('10.244.25.77') }}" tags: aixiu_web tasks: - name: insert after lineinfile: dest: /usr/local/nginx/conf/nginx.conf insertafter: '((.*)(\"agent\"\:\"\$http_user_agent\")(.*))' line: '\2"http_cdn_src_ip":"$http_cdn_src_ip"\4' regexp: '((.*)(\"agent\"\:\"\$http_user_agent\")(.*))' backrefs: yes tags: insertafter $ansible-playbook iweb_regexp.yml -C -D -t insertafter PLAY [insertafter with regexp] ************************************************************************************************************** TASK [insert after] ************************************************************************************************************************* --- before: /usr/local/nginx/conf/nginx.conf (content) +++ after: /usr/local/nginx/conf/nginx.conf (content) @@ -65,7 +65,7 @@ '"request_uri":"$request_uri",' '"xff":"$http_x_forwarded_for",' '"referer":"$http_referer",' - '"agent":"$http_user_agent",' + '"http_cdn_src_ip":"$http_cdn_src_ip",' '"status":"$status"}'; access_log /data/logs/nginx/access.log main; 最后,基础研究,正则替换的基础知识: 对于斜杠来讲,在PHP里即使是单引号,它也是会和类似双引号里的$一样,有被转义: '/\\\\/' cat a.php php a.php \' cat reg3.php string(1) "\" } 拓展,在Ansible里是Python正则替换时遇到 $符号时会有三个斜杠(http://jackxiang.com/post/8857/),而为什么是三个斜杠呢? ansible 10.71.11.39 -m lineinfile -a "backrefs=yes dest=/usr/local/nginx/conf/nginx.conf regexp='((.*)(\"agent\":\"\\\$http_user_agent\")(.*))' line='\1\n\2\"http_cdn_src_ip\":\"\$http_cdn_src_ip\"\4'" -C -D 最外层-a""双引,不看regexp的单引号。 reg.php string(1) "\" } RegexBuddy/Patterns的使用方法和来自:https://www.jb51.net/article/104895.htm的一个对照,如下: test.php 2222222<\/td>3$'; //'\\\\\/' 第1个'\'转义字符串的第2个'\',字符串为'\' //第3个'\'转义第4个'\',相当于字符串'\' //第5个'\'转义第4个'/',相当于字符串'/' //字符合起来为'\\/' 两个'\\' 正则表达式看做'\' //$pattern = '/([0-9]{7,})<\\\\\/td>\d\\$$/'; //最后一个$是正则结尾的意思 $pattern = '/([0-9]{7,})<\\\\\/td>\d\\$/'; //可以不要正则结尾的$,对3$的$直接转义,加上单引号,不用再转一次,也就是前面的第三个斜杠。 $result = preg_match_all($pattern, $content, $match_result); if($result) print_r($match_result); else echo("not match"); 正则猫RegexBuddy,Match--> 选个Python吧,能匹配的正则表达式: [0-9]{7,}<\\/td>\d\$$ Test: 11111112222222<\/td>3$ 而上文里多的斜杠,从哪儿得出来呢? Copy->CopyRegex as...->CopyRegex as PHP string '[0-9]{7,}<\\\\/td>\d\$$' Copy->CopyRegex as...->CopyRegex as PHP '//' preg String '%[0-9]{7,}<\\\\/td>\d\$$%' ,替换为最左最右斜杠: '/([0-9]{7,})<\\\\/td>\d\$$/' #正则猫的正则拷贝Copy结果,加上数字匹配的括号 '/([0-9]{7,})<\\\\\/td>\d\\$/' #PHP能成功运行并找到正确匹配结果的字符串正则,上面四个斜杠多两个斜杠,\d后面多一个斜杠。 也就是说放到PHP里面还得对斜杠再次转义,让正则认为它是真正的斜杠,而不是PHP里的转义符。 为何四个斜杠加两个斜杠?正则里的反斜杠转义为正则里的字符,但引号里的反斜杠还得转为PHP的正常认为的一个反斜杠,所以得加两个斜杠: 摘自:https://blog.csdn.net/xiaoxiaoniaoer1/article/details/7717669 "\\"==> 反斜杠 在双引号内使用这些字符时,它们具有特殊的含义 转义字符代码 转义字符的含义 \ " 双引号 \ ' 单引号 \ \ 反斜杠 \ n 换行符 \ r 回车符 \ t 制表符 \ $ 美元符号 实践发现放正则里的内容于外面是双引号单引号无关,只是双引号要转义,只要内容一样,匹配也一样的: string(2) "$a" } 然而,如果preg_match里的正则用单引号,那就匹配不出来了,如下: string(2) "$a" } 反过来说明啥?说明正则猫RegexBuddy默认是单引进行PHP的匹配,所以前面不用再加两个斜杠转义,证明下: RegexBuddy采用双引号,也就无法匹配了: php test.php not match 得证: $pattern = "%([0-9]{7,})<\\\\/td>\d\$$%"; //not match $pattern = "%([0-9]{7,})<\\\\\\/td>\d\\$$%"; //matched $pattern = '%([0-9]{7,})<\\\\/td>\d\$$%'; //matched 也就是说RegexBuddy已经指明用单引号了,再回头看前面的: Copy->CopyRegex as...->CopyRegex as PHP '//' preg String <----它本来就是一个单引号'//' ,指明了,自己瞎搞要用双引号,双引号就再加一个对斜杠的转义即可!!! 回到背景里的这个Ansible,Ansible是用Python写的本质上是一样的道理: -C, --check don't make any changes; instead, try to predict some of the changes that may occur -D, --diff when changing (small) files and templates, show the differences in those files; works great with --check ansible 10.71.11.39 -m lineinfile -a "backrefs=yes dest=/usr/local/nginx/conf/nginx.conf regexp='((.*)(\"agent\":\"\\\$http_user_agent\")(.*))' line='\1\n\2\"http_cdn_src_ip\":\"\$http_cdn_src_ip\"\4'" -C -D '"referer":"$http_referer",' '"agent":"$http_user_agent",' '"http_cdn_src_ip":"$http_cdn_src_ip",' + '"http_cdn_src_ip":"$http_cdn_src_ip",' '"status":"$status"}'; access_log /data/logs/nginx/access.log main; 10.71.11.39 | SUCCESS => { "backup": "", "changed": true, "msg": "line replaced" } 而前面用的是双引号,那么改成单引号怎么修改呢?和上面这个还不一样,它是写入到Client端后再运行的,有点不一样,用正则猫试试: Match: ((.*)(\"agent\":\"\$http_user_agent\")(.*)) Test: '"agent":"$http_user_agent",' 拷贝出来: '/((.*)(\"agent\":\"\$http_user_agent\")(.*))/' 之前那个: Match: ([0-9]{7,})<\\/td>\d\$$ Test: 11111112222222<\/td>3$ 拷贝出来: '%([0-9]{7,})<\\\\/td>\d\$$%' 发现没,那个斜杠是多到四个,但是后面的$并没有加斜杠,说明这只猫并不聪明,还得靠人人为把: \$整成 \\$ 而PHP里对这个 $前的斜杠并不敏感: $pattern = '%([0-9]{7,})<\\\\/td>\d\\$$%'; $pattern = '%([0-9]{7,})<\\\\/td>\d\\$$%'; 都能匹配出来: #php test.php Array ( [0] => Array ( [0] => 2222222<\/td>3$ ) [1] => Array ( [0] => 2222222 ) ) 总之,不能全信正则猫,实践得出单引号会少用斜杠,双引号就要多用斜杠。: 2222222<\/td>3$'; $pattern = "%([0-9]{7,})<\\\\\\/td>\d\\$%"; //matched //$pattern = '%([0-9]{7,})<\\\\/td>\d\$%'; //matched $result = preg_match_all($pattern, $content, $match_result); if($result) print_r($match_result); else echo("not match"); EOF Generated by Jackxiang's Bo-blog 2.1.1 Release