Newline('\n') is replaced by space(' ') in ansible shell module - bash

I have an ansible task where I make use of shell module to run a custom bash script. This bash script expects an optional parameter which could be a multiline string. Example
~/myScript.sh -e "Each \nword \nin \nnew \nline"
This works perfectly fine when I run it in the bash shell. However, I have tried several ways but haven't made it run via the ansible task. The value being a variable itself. The task below -
- name: Sample Task
shell:
cmd: "{{ home_dir }}/myScript.sh -e '{{ string_with_escaped_newlines }}'"
Here, the value of variable string_with_escaped_newlines is set to Each \nword \nin \nnew \nline programmatically by other tasks. The output of the task confirms this.
I had put an echo in my shell script to debug & I observe that the echo would print 2 different sequences in the 2 cases(running directly via shell & running via ansible).
Debug output via shell-
Each
word
in
new
line
Debug output via ansible -
Each word in new line
Notice that there is an extra space introduced in the value in place on \n. I do not understand why this happens and how do I stop this. I have checked/tried shell as well as command modules of ansible.

cmd: "{{ home_dir }}/myScript.sh -e '{{ string_with_escaped_newlines }}'"
is first converted (by Jinja2) to
cmd: "<THE HOME>/myScript.sh -e 'Each \nword \nin \nnew \nline'"
Here \n is within double quotes and in YAML double quoted \ns are NEWLINEs (think "\n" in C or JSON).
For your case you can just write:
cmd: {{ home_dir }}/myScript.sh -e '{{ string_with_escaped_newlines }}'
Or to be more safe:
cmd: |
{{ home_dir }}/myScript.sh -e '{{ string_with_escaped_newlines }}'
YAML syntax is quite "confusing". You can take a look at Ansible's own YAML intro.

Given the script
shell> cat ~/myScript.sh
#!/usr/bin/sh
echo ${1}
shell> ~/myScript.sh "Each \nword \nin \nnew \nline"
Each
word
in
new
line
Q: "Newline '\n' is replaced by space ' ' in Ansible shell module."
A: It depends on how you quote and escape the string. The simplest option is single-quoted style because only ' needs to be escaped
string_with_escaped_newlines1: 'Each \nword \nin \nnew \nline'
You have to escape \n in double-quoted style
string_with_escaped_newlines2: "Each \\nword \\nin \\nnew \\nline"
Both variables expand to the same string
- debug:
var: string_with_escaped_newlines1
- debug:
var: string_with_escaped_newlines2
gives
string_with_escaped_newlines1: Each \nword \nin \nnew \nline
string_with_escaped_newlines2: Each \nword \nin \nnew \nline
Then, the quotation in the command is not significant. All options below give the same result
- command: ~/myScript.sh "{{ string_with_escaped_newlines1 }}"
- command: ~/myScript.sh '{{ string_with_escaped_newlines1 }}'
- command: ~/myScript.sh "{{ string_with_escaped_newlines2 }}"
- command: ~/myScript.sh '{{ string_with_escaped_newlines2 }}'
Example of a complete playbook for testing
- hosts: localhost
vars:
string_with_escaped_newlines1: 'Each \nword \nin \nnew \nline'
string_with_escaped_newlines2: "Each \\nword \\nin \\nnew \\nline"
tasks:
- command: ~/myScript.sh "{{ string_with_escaped_newlines1 }}"
register: out
- debug:
var: out.stdout_lines
- command: ~/myScript.sh '{{ string_with_escaped_newlines1 }}'
register: out
- debug:
var: out.stdout_lines
- command: ~/myScript.sh "{{ string_with_escaped_newlines2 }}"
register: out
- debug:
var: out.stdout_lines
- command: ~/myScript.sh '{{ string_with_escaped_newlines2 }}'
register: out
- debug:
var: out.stdout_lines
gives (abridged) four times the same result
TASK [debug] *********************************************************
ok: [localhost] =>
out.stdout_lines:
- 'Each '
- 'word '
- 'in '
- 'new '
- line
Notes:
Using shell instead of command gives the same results.
The best practice is using the module command unless the ansible.builtin.shell module is explicitly required.

Have you tried \r\n instead of \n
It helped me elsewhere in ansible

Related

run different command in the loop based on the item attributes in ansible

here I got an issue that I need run a command based on an list of items, like the following code:
- name: Task command
command: "command1 keyword1 --param1 {{ item.attribute1 }}"
with_items:
- "{{ list_of_items }}"
but now, I need to update the playbook to add more features, such as when item.attribute1 == 'a', still run the above command, but otherwise, the command should be something like this
command: "command1 keyword2 --param2 {{ item.attribute1 }}" # completely different from the above command!
is there a way in ansible to support this kind of syntax that support running different command when use the loop?
Appreciate to the help of any kinds.
Is this what are you looking for?
- hosts: localhost
vars:
commands:
a: 'command1 keyword1 --param1'
b: 'command1 keyword2 --param2'
c: 'command1 keyword3 --param3'
tasks:
- debug:
msg: "{{ commands[item.attribute1] + ' ' + item.attribute1 }}"
loop:
- {attribute1: 'a'}
- {attribute1: 'b'}
- {attribute1: 'c'}
gives
"msg": "command1 keyword1 --param1 a"
"msg": "command1 keyword2 --param2 b"
"msg": "command1 keyword3 --param3 c"

Ansible Jinja split on backslash fails

I have a ansible play that tries to split a string in the format of domain\user into its parts:
This is the task
tasks:
- name: do something which requires domain and user
win_shell: echo "{{ lookup('aws_ssm', 'service_user-account-2921', decrypt=True, region='eu-central-1' )}}.split('\\')[0] }}"
This results in:
ERROR! failed at splitting arguments, either an unbalanced jinja2 block or quotes: {{'DOMAIN\USER'.split('\')[0]}}
if I change the task to remove the lookup it still fails as long as I use \ as delimiter
#win_shell: echo "{{ 'test,strings'.split(',')[0] }}" #WORKS
win_shell: echo "{{ 'DOMAIN\\USER'.split('\\')[0]}}" #FAILS
how to split on a backslash in ansible / jinja?
Q: "How to split on a backslash in ansible/jinja?"
A: Put the separator into a variable. For example
vars:
separator: '\'
text: 'domain\user'
tasks:
- debug:
msg: "{{ text.split(separator) }}"
gives
"msg": [
"domain",
"user"
]

Ansible not escaping windows path first argument

I have playbook with windows path name in the extra arguments. first argument not escaping the drive letter and slash.
ansible-playbook d.yaml --extra-vars "ainstalldir=c:\\test stagedir=D:\packages outdir=d:\output\log"
TASK [print inpurt arguments] ********************************************************************************************************
ok: [127.0.0.1] => {
"msg": "installdir=c:\test, stragedir=D:\\packages, outdir=d:\\output\\log"
}
installdir prints as c:\test, I expect it should print as c:\\test
Here is my playbook.
---
- name: test command line arguments
connection: local
hosts: 127.0.0.1
gather_facts: false
vars:
installdir: "{{ ainstalldir }}"
stagedir: "{{ stagedir }}"
outdir: "{{ outdir }}"
tasks:
- name: print inpurt arguments
debug:
msg="installdir={{ installdir }}, stragedir={{ stagedir }}, outdir={{ outdir }}"
Any idea how to resolve this issue?
installdir prints as c:\test, I expect it should print as c:\\test
installdir contains: c : tab e s t.
tab is replaced with \t in the debug module output and in effect you see c:\test on the screen.
Other characters starting with backslash in your example (\p, \o, \l) do not have special meaning, so they are treated as two character strings; but you'd observe the same phenomenon with \n (and other escape sequences).
Don't use debug module to debug things concerned with data, it processes strings to make them printable.
Instead, use copy with content parameter and check the output in a file:
- copy:
content: |-
installdir={{ installdir }}
stragedir={{ stagedir }}
outdir={{ outdir }}
dest: ./result.txt
(remember you could/should use hexdump to verify what's really inside).
Use:
ansible-playbook d.yaml --extra-vars "ainstalldir=c:\\\test stagedir=D:\\\packages outdir=d:\\\output\\\log"
or
ansible-playbook d.yaml --extra-vars 'ainstalldir=c:\\test stagedir=D:\\packages outdir=d:\\output\\log'
Backslashes in double- and single quotes are interpreted differently by shell (see for example this question).

ansible read (more than one) values from a line in file

i am trying to read variables from a file into an array of something, but not into a pre defined variable.
The goal is a playbook, that searches for lines with item.0 and NOT item.1 and deletes them, later the playbook makes sure, that a line with item.0 item.1 is present, thats why i need this splitted.
For example i have a file with lines like this:
Parameter Value
Parameter Value
Parameter Value
to use this in a loop until EOF.
example part ot the playbook:
- name: lineinfile loop
lineinfile:
path: /myfile
regexp '^{{somethinglike item.0}}.(?!{{something like item.1}})'
state absent
...
Does anybody know a solution for the lookup and the loop?
Best regards
This should help you.
Input file
cat filein
Parameter1,Value1
Parameter2,Value2
Parameter3,Value3
Playbook:
---
- hosts: test
tasks:
- name: reg
shell: cat filein
register: myitems
- name: deb
debug: var=myitems.stdout_lines
- name: echo to file
shell: "echo {{ item.split(',')[1] }} - {{ item.split(',')[0] }} >> fileout"
with_items: "{{ myitems.stdout_lines }}"
Output file:
cat fileout
Value1 - Parameter1
Value2 - Parameter2
Value3 - Parameter3
This is not state of the art but should work.
The solutuion:
There is a ansible role in ansible galaxy, it provides exactly what i need:
https://github.com/mkouhei/ansible-role-includecsv

In Ansible, how do I export an environment variable in one task so it can be used by another task?

Say I have the following file
foo.txt
Hello world=1
Hello world=2
...
Hello world=N
I want to use Ansible to insert another Hello world line, like this
foo.txt
Hello world=1
Hello world=2
...
Hello world=N
Hello world=N+1
IOW, I don't know home many Hello world lines there are, I need to find out via script, and get the value of N.
In Ansible, say I have the following shell task
- name: Find the last Hello world in foo.txt, and get the value of N
shell: >
# Get last Hello world line in foo.txt. I want to export this as an
# environment variable
LAST_HW_LINE=`awk '/Hello world/ {aline=$0} END{print aline}' "foo.txt"`
# Get left side of equation
IFS='=' read -ra LS <<< "$LAST_HW_LINE"
# Get the value of N
NUM=${LS##*\.}
# Increment by 1. I want to export this as an environment variable
NUM=$((NUM+1))
I want to be able to subsequently do this
- name: Add the next Hello world line
lineinfile:
dest: foo.txt
insertafter: "{{ lookup('env', 'LAST_HW_LINE') }}"
line: "Hello world={{ lookup('env', 'NUM') }}"
Maybe there's a better way to do this than using environment variables?
Register Variables
- shell: /usr/bin/foo
register: foo_result
Registering stdout as ansible variables
- debug: msg="{{ hello.stdout }}"
- debug: msg="{{ hello.stderr }}"
Including task with variables:
tasks:
- include: wordpress.yml wp_user=timmy
Including role with variables
- hosts: webservers
roles:
- common
- { role: foo_app_instance, dir: '/opt/a', app_port: 5000 }
For your case:
- name: so question 39041208
hosts: '{{ target | default("all") }}'
tasks:
- name: Find the last Hello world in foo.txt, and get the value of N
# shell: awk '/Hello world/ {aline=$0} END{print aline}' "/home/ak/ansible/stackoverflow/q39041208.txt"
shell: awk '/Hello world/ {aline=$0} END{print NR}' "/home/ak/ansible/stackoverflow/q39041208.txt"
register: last_line
ignore_errors: true
- name: debug
debug: msg="last line {{ last_line.stdout }}"
- name: debug next number
debug: msg="next num {{ last_line.stdout | int + 1 }}"
- name: Add the next Hello world line
lineinfile:
dest: /home/ak/ansible/stackoverflow/q39041208.txt
insertafter: "{{ last_line.stdout }}"
line: "Hello world={{ last_line.stdout | int + 1 }}"

Resources