Ansible provides a rich pattern matching ability. Modules like lineinfile can match strings based on regular expressions. Similar expressions are used in Python, Perl and older tools such as egrep, grep, sed and awk.
When attempting to match a string containing awkward characters, an escape mechanism can be used. For example, the dollar character ($) has a special meaning within in a regular expression, being the match for end-of-line. So to match a literal dollar, an escape character, usually a backslash (\), is needed. For example, the regular expression “\$1.65” will successfully match $1.65, without treating $ as end of line.
When processing a string that contains many special characters, the escape syntax can become onerous. One solution is to just “blanket” match the special character, rather than trying to match it precisely. In other words: just use a dot.
Example: Instrumenting a PHP File
Below is an Ansible task designed to process a fairly large PHP file. It’s the source for Encode Explorer, a simple, and highly attractive, file storage solution.
We want to create a user for the Encode Explorer application, by replacing this line in the PHP:
$_CONFIG['users'] = array();)
with this:
$_CONFIG['users'] = array(array("frederick", "'password'", "admin"));
It should be a fairly simple job. Here’s the Ansible:
- name: Create Encode Explorer account lineinfile: dest: 'index.php' regexp: ^\\$_CONFIG\\['users'\\] = .*$ line: $_CONFIG['users'] = array(array("frederick", "password", "admin"));
To match the original line ($_CONFIG[‘users’] = array();), we have a suitable regexp: instruction. It contains a heavily escaped sequence to match the text on the left hand side of the equals, and just a “.*$” to match what is on the right. The multiple escapes are needed to perform a literal match on the special characters $, [, and ].
Testing
It doesn’t work (as tested with Ansible 1.5.4). That is to say: the regexp: doesn’t match. It ought to match, but doesn’t. The regular expression might be wrong, or there might be a bug in this version of Ansible, or the module, or some other anomaly might be the cause. Ansible regular expressions and escaping are the subject of much discussion, as a quick search on Google will testify.
Just use a Dot
Rather than spend time testing and adjusting that one regular expression, I just matched the awkward characters with a dot (.), the single character match operator.
- name: Create Encode Explorer account lineinfile: dest: 'index.php' regexp: "^._CONFIG.'users'. = .*$" line: $_CONFIG['users'] = array(array("frederick", "password", "admin"));
Now the task works. The simplified expression is enough to uniquely match that one line in the file (of over 3000 lines).
Conclusion
Much time can be saved by matching special characters with a simple dot, rather than using more complicated escape sequences. Sorry if this seems obvious. The Ansible RE framework is, dare I say it, a touch semi-deterministic, and much time can be absorbed in the testing. Instead, use the dot to give a much simpler RE that still gives the desired matching behaviour.
Note: In the example, a user “frederick” is created with password “password”. Obviously that was changed for the article. A password of “password” would never be used, for security reasons.
Regex saves a lot of time and effort. I always use them in Sed and AWK.
Thanks for the article.