Ansible remove last character from string if its a number - ansible

I have a list like the following
fruit: [ 'apple','orange2','grape3apple1']
I want an output like the following and want this to be stored in another var for further use
fruit_out: [ 'apple','orange','grape3apple' ]
If the last character is a number, I want the number stripped, if not, I need the string returned as is. I have looked around a bit without much luck getting any usable solution.

Reiterating my comment for context:
Given a string variable somevariable, somevariable[-1] is the last character, and somevariable[:-1] is the string not including the last character. –
I am looking for a validation to check if the last character is indeed a number. If there is a number at in the middle it should not count for the check
Ansible uses Python, so you can use any Python string method in your Ansible playbooks. For example, to have a task execute when a value ends with a number, you could write:
- debug:
msg: Your variable ends with a number!
when: somevariable[-1].isdigit()
You could perform the same check using a regular expression filter:
- debug:
msg: Your variable ends with a number!
when: somevariable is search('[0-9]$')

One possibility would be to use a regex and the filter regex_search.
So, given the task:
- debug:
msg: >-
{{
fruits
| map('regex_search', '^(.*?)[0-9]?$', '\1')
| map('first')
}}
vars:
fruits:
- apple
- orange2
- grape3apple1
This yields the expected:
ok: [localhost] =>
msg:
- apple
- orange
- grape3apple
The only tricky part in that regex is that you have to make your capturing group lazy, with the help of the lazy quantifier *?, otherwise your matched group will spawn over the unwanted number.

Use the filter regex_replace. For example, put the declarations below as appropriate
fruit: [apple, orange2, grape3apple1]
fruit_regex: '^(.+?)\d*$'
fruit_replace: '\1'
fruit_out: "{{ fruit|map('regex_replace', fruit_regex, fruit_replace)|list }}"
gives
fruit_out:
- apple
- orange
- grape3apple
Explanation of the regex
^ ....... matches the beginning of a string
(.+?) ... creates a capture group
. ... matches any character
+? ... 1 or more (append ? for non-greedy)
\d* ... digit, 0 or more
$ ....... matches the end of a string
Example of a complete playbook
- hosts: localhost
vars:
fruit: [apple, orange2, grape3apple1]
fruit_regex: '^(.+?)\d*$'
fruit_replace: '\1'
fruit_out: "{{ fruit|map('regex_replace', fruit_regex, fruit_replace)|list }}"
tasks:
- debug:
var: fruit_out

Related

Ansible regex_findall is not giving expected results with negative search

My ansible negative regex is returning everything
So I'm getting results of dns names from JSON, of which I'm trying to sort to create an automated ansible inventory.
I have servers with naming schemes such as:
abc02vsomeserver01.subdomain1.domain.gov
abc02someserver01.subdomain1.domain.gov
xyz03votherserver11.subdomain2.domain.gov
wyz03otherserver11.subdomain2.domain.gov
qrsmainserver02.maindomain.domain.gov
I'm getting the "v" servers divided out by environment, then I want to create a catchall group that is not the "v" servers
So! I'm attempting to do:
{{ jsonOutput | json_query('json.response.results[].dnsName') | regex_findall('(?![a-z]{3}[0-9]{2}v)^.*', multiline=true) }}
Which does seem to work when I plug it into https://pythex.org.
But ansible is returning everything instead...
What the heck am I doing wrong here?
It's because that json_query emits a list[str] which when fed directly into regex_findall doesn't become a newline delimited string, it becomes the same as str(["alpha", "beta"]) (e.g. ['alpha', 'beta']) and then the multiline regex fails to do what you are expecting
There are, as with many things in life, a few ways to fix that. One is to just feed the results into |join("\n") and then you're likely back where you thought you were to begin with:
- debug:
msg: "{{ jsonOutput | json_query('json.response.results[].dnsName') | join('\n') | regex_findall('(?![a-z]{3}[0-9]{2}v)^.*', multiline=true) }}"
The other is to acknowledge that it's a list[str] and use the | select("match", ...) filter to only allow though items that match:
- debug:
msg: >-
{{ response | json_query('results[].dnsName')
| select('match', '(?![a-z]{3}[0-9]{2}v)^.*')
| list }}
vars:
response:
results:
- dnsName: abc02vsomeserver01.subdomain1.domain.gov
- dnsName: abc02someserver01.subdomain1.domain.gov
- dnsName: xyz03votherserver11.subdomain2.domain.gov
- dnsName: wyz03otherserver11.subdomain2.domain.gov
- dnsName: qrsmainserver02.maindomain.domain.gov
similarly produces:
"msg": [
"abc02someserver01.subdomain1.domain.gov",
"wyz03otherserver11.subdomain2.domain.gov",
"qrsmainserver02.maindomain.domain.gov"
]
I would guess it's personal preference which style works best in your playbook

ansible how to get last 2 digit of number

I have question about how to convert a digit to string and then get last 2 number using filter
For string, it's easy to use like:
vars:
string_1: 'abcd'
number_1: 1234
For string, it's easy:
"{{ string_1[-2:] }}"
But for number then i have to convert to string first but it failed while templating.
"{{ number_1 | string | [-2:] }}
How can I achieve this in single line code?
Close the conversion in parenthesis. The index has higher precedence compared to the filter. (And index can't be used as a filter, of course).
msg: "{{ (number_1|string)[-2:] }}"
The only difference is that modulus % returns an integer. The tasks
- debug:
msg: "{{ (number_1 % 100)|type_debug }}"
- debug:
msg: "{{ (number_1|string)[-2:]|type_debug }}"
give
"msg": "int"
"msg": "str"
You may use a modulus trick here:
{{ number_1 % 100 }}
The modulus dividing by 100 should yield the final two digits of any number input.

Ansible regex_replace insert value after regex match

I have a version variable, which I need to split and insert a . in between
I tried this
ansible localhost -e version=300 -m debug -a "msg={{ version | regex_replace('\d{1,2}', '.\g<0>' ) }}"
But the o/p is
TASK [debug] ********************************************************************************************************************************************************************************************************************************
ok: [localhost] =>
msg: .30.0
There is a . getting .30.0 added in the first place. I can use regex_repace to remove the first. after that.
But is there any other better way? Why is pattern putting the decimal point in the first place?
Q: "Why is pattern putting the decimal point in the first place?"
A: Regex \d{1,2} matches one or two digits. Given the string 300 this regex matches the first two digits30. It will be replaced with .\g<0> which gives
.30
Next match is 0 because only one digit has left. The replacement gives
.0
Put together the result is
.30.0
Q: "Is there any way I can directly insert "." (dot) after the second place? ie 30.0?"
A: For example the play
- hosts: localhost
vars:
my_string: '300'
tasks:
- debug:
msg: "{{ my_string[0:2] ~ '.' ~ my_string[2:] }}"
- debug:
msg: "{{ my_string[:-1] ~ '.' ~ my_string[-1] }}"
gives
"msg": "30.0"
"msg": "30.0"

How can I manipuate each item in a list when using Ansible?

The special variable ansible_role_names contains a list with roles names in my play. For example
"ansible_role_names": [
"geerlingguy.nfs",
"ANXS.openssh",
"robertdebock.bootstrap",
"robertdebock.squid"
]
However what I want to access is this list with everything before the dot removed.
"roles": [
"nfs",
"openssh",
"bootstrap",
"squid" ]
Q: "How to manipulate each item in an array when using Ansible?"
A: It's possible to map the regex_replace filter. For example the play
- set_fact:
my_list: "{{ ansible_role_names|
map('regex_replace', regex, replace)|
list }}"
vars:
regex: '^(.*)\.(.*)$'
replace: '\2'
- debug:
var: my_list
gives
"my_list": [
"nfs",
"openssh",
"bootstrap",
"squid"
]
regex: '^(.*)\.(.*)$'
^ matches the beginning of the string
(.*) matches any character in capture group No.1
\. matches .
(.*) matches any character in capture group No.2
$ matches the end of the string
replace: '\2'
\2 matches previously defined capture group No.2
Building-up on #Arbab Nazar answer: splitting the strings on dots and using index 1 on the resulting list might break on some occasions.
If your role name does not contain a dot (pretty common for local roles), you will get an error with an undefined variable => list object has no element 1
In the (unlikely...) case your role has several dots in its name, you will always get the second element in the string, not the last
Using -1 as the index (e.g. first element starting from the end) will fix both of those potential problems like in the following example:
- debug:
msg: "{{ item.split('.')[-1] }}"
loop:
- simplerole
- classic.galaxyrole
- non.standard.customrole
Which gives the following result:
TASK [debug] ************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=simplerole) => {
"msg": "simplerole"
}
ok: [localhost] => (item=classic.galaxyrole) => {
"msg": "galaxyrole"
}
ok: [localhost] => (item=non.standard.customrole) => {
"msg": "customrole"
}
You can get it by using the split filter:
- debug:
msg: "{{ item.split('.')[-1] }}"
loop: "{{ ansible_role_names }}"

Ansible truncate concatentated string

I am generating an yaml template in Ansible and I am trying to truncate two concatenated strings:
Here the following code doesn't work because of the concatenation does not pipe into the regex_replace correctly.
I am only wanting the first n characters (first 10 characters in this example)
Normally I could just combine these two into one variable and then do
{{variabel [:10] }}
But I am no able to do that in this case because the file I am working in is getting combined with variables and then saved as a yaml file...
Basically I want to truncate the string without first combining or creating a new variable.
- hosts: localhost
gather_facts: False
vars:
foo: "somelongstring"
tasks:
- name: Display debug output
debug:
msg: "{{ foo + '-moretext' | regex_replace('^.{0,10}', '\\1') }} "
To apply a filter or an operator on a complex expression (other than a sequence of filters), you have to surround it with parenthesis.
So to truncate the result of a concatenation in 1 action:
msg: "{{ (foo + '-moretext')[:10] }} "
BTW, there is also the truncate filter:
msg: "{{ (foo + '-moretext') | truncate(10, True, '') }} "

Resources