How to pass array to ansible extra-vars from bash script - linux

I'm trying to write a bash script that will invoke ansible playbook with extra-vars. Some of this vars is an array of strings with spaces. So i am very confused in how to pass them correctly. I already tried many combinations of quotes and slashes. So I came here for help.
ansible-playbook -i inventory.yml playbook.yml --extra-vars \
'username="${login}" fullname="${username}" password="${password}" groups="['Users','Remote Desktop Users']"';

This is what you need to know about bash variables and quoting:
For the following examples, the variable ${text} is set to Hello:
Variables are expanded inside double quotes. e.g. "${text}" => Hello
Variables are not expanded inside single quotes. e.g. '${text}' => ${text}
Single quotes have no special meaning inside double quotes and vice-versa. e.g. "'${text}'" => 'Hello' and '"${text}"' => "${text}"
If you need to place a double-quote inside a double-quoted string, or a single quote inside a single-quoted string, then it must be escaped. e.g. "\"${text}\"" => "Hello" and '\'${text}\'' => '${text}'
With all that said, in your case, you want the variables to be expanded, so you should enclose the entire --extra-vars value in double quotes. According to the Ansible website, the value of each of these extra variables does not need to be quoted, unless it contains spaces. To be safe, you can quote the variables as you might not be able to control their values.
Try this. I have added extra line breaks to make the code easier to understand:
ansible-playbook -i inventory.yml playbook.yml --extra-vars \
"username='${login}' \
fullname='${username}' \
password='${password}' \
groups=['Users','Remote Desktop Users'] \
"

It seems that variable name 'groups' is reserved by ansible.
I changed name, and script starts working.
The answer of Andrew Vickers is also correct.

Related

escape one variable but not the other in awk

I'm facing a problem with awk in Linux. I would like to do make this script work :
awk -v var="$MYVAR" "{gsub(/export OTHER_VAR=\$OTHER_VAR:/, "var")}1" /etc/myfile
The problem here is that I want the variable "var" to be interpreted (it works) and the variable $OTHERVAR not to be interpreted, and this I what I can't manage to do.
In the end, I want to do this:
I have a variable
MYVAR=export OTHER_VAR=\$OTHER_VAR:some_text
I want to replace, in /etc/myfile, the following pattern :
export OTHER_VAR=$OTHER_VAR:/folder/bin by export OTHER_VAR=$OTHER_VAR:some_text:/folder/bin.
I hope I made myself clear ...
Thanks in advance !
Sylvain
test_document='export OTHER_VAR=$OTHER_VAR:whatever'
search_regex='^export OTHER_VAR=[$]OTHER_VAR:'
replace_str='export OTHER_VAR=$OTHER_VAR:some_text:'
awk -v search_regex="$search_regex" \
-v replace_str="$replace_str" \
'{gsub(search_regex, replace_str)} {print}' <<<"$test_document"
...properly emits as output:
export OTHER_VAR=$OTHER_VAR:some_text:whatever
Note some changes:
We're escaping the $ in the regex as [$]. Unlike \$, this is parsed consistently across all quoting contexts: It is explicitly generating a regex character class, rather than having any other potential meaning.
Using single quotes for literal strings ensures that no shell interpolation takes place within them.
Using {print} is a bit easier for readers to understand than a bare 1 in awk.
Excluding variable names with meaning to the OS or shell, use of lower-case characters in variable names is in line with POSIX-specified convention. See http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html, fourth paragraph.

bash empty variable command substitution leads to a single quote [duplicate]

This question already has answers here:
Bash inserting quotes into string before execution
(2 answers)
Closed 5 years ago.
The following is a script where the empty variable is replaced by single quotes. If the variable is not empty then we have the correct substitution.
$ set -x; aa=""; bb=`curl "$aa" -vvv`; set +x
+ aa=
++ curl '' -vvv
The thing to notice is the single quotes in place of the empty variable.
When variable is not empty, everything works fine as in:
$ set -x; aa="google.com"; bb=`curl "$aa" -vvv`; set +x
+ aa=google.com
++ curl google.com -vvv
Q1: Why is an empty variable or a variable with space resulting in single quotes being introduced?
Q2: How do I prevent the single quotes in lieu of the empty variable?
Now, I can remove the double quotes and everything works fine but I need to preserve spaces if there are any.
Thanks.
Single quotes are not being introduced, they are just being used to show you that you are passing an empty argument. The double quotes you use are causing the empty argument to be passed. If you want no argument but want to allow whitespace in the variable, you'll have to do something like:
curl ${aa:+"$aa"} -vvv
This is different than curl ${aa+"$aa"} -vvv when aa is the empty string, but from your description it appears you want the former.
You are seeing the single quotes because you used double quotes around $aa, so curl sees the empty string as your parameter. You won't get this effect if you drop the double quotes like this:
set -x; aa=""; bb=`curl $aa -vvv`; set +x
This is because you enclosed variable substitution into double quotes. $aa expands to empty string, which is placed into quotes, therefore producing quotes with nothing. In this case curl will see 3 arguments in that order: curl, <empty string>, -vvv.
If you remove double quotes round $aa, the second argument will not be passed to curl, and it will see only curl and -vvv in argv parameter in it's main function:
$ set -x; aa=""; bb=`curl $aa -vvv`; set +x
+ aa=
++ curl -vvv

Can I prevent echo from expanding env vars?

I have an automatic setup script that is executed when a new user is created. It runs this line of code to set up the golang environment: echo "export PATH="$PATH:$GOPATH/bin"" >> ~/.profile
But this will expand all environment variables before writing into the file. Is there a way to write export PATH="$PATH:$GOPATH/bin" into a file from the command line without expanding the environment variables?
Try:
echo 'export PATH="$PATH:$GOPATH/bin"' >> ~/.profile
Single-quoted strings in POSIX-like shells (such as bash) treat their content as literals, which is what you want here.
The only reason to use a double-quoted string here would be to selectively expand variable references up front - which doesn't apply in your case.
That said, here's an example:
$ echo "Honey, I'm \"$USER\" and I'm \$HOME."
Honey, I'm "jdoe" and I'm $HOME.
Backslash-escaping is used to escape embedded " and $ instances that should be treated as literals.
As for what you tried:
"export PATH="$PATH:$GOPATH/bin""
is actually a string concatentation, composed of 3 separate strings:
"export PATH=", which, as a double-quoted string that happens not to contains $-prefixed interpolation elements, expands to literal export PATH=
$PATH:$GOPATH/bin, which, as an unquoted string, is subject to additional shell expansions, which not only involves expanding variables $PATH and $GOPATH to their respective values, but also applies word-splitting and pathname expansion (globbing).
"", which amounts to the empty string and is effectively ignored.
Note how POSIX-like shells allow you to compose larger strings (concatenate strings) by placing strings - unquoted or single-quoted or double-quoted - directly next to one another.

Multiword string as a curl option using Bash

I want to get some data from a HTTP server. What it sends me depends on what I put in a POST request.
What I put in the INPUT_TEXT field is a sequence of words. When I run the following command, I get good looking output.
$ curl http://localhost:59125/process -d INPUT_TEXT="here are some words"
I want a bash script to take some string as a command line argument, and pass it appropriately to curl. The first thing I tried was to put the following in a script:
sentence=$1
command="curl http://localhost:59125/process -d INPUT_TEXT=\"${sentence}\""
$command
I then run the script like so:
$ ./script "here are some words"
But then I get a curl Couldn't resolve host error for each of "are", "some", and "words". It would seem that "here" got correctly treated as the INPUT_TEXT, but the rest of the words were then considered to be hosts, and not part of the option.
So I tried:
command=("curl" "http://localhost:59125/process" "-d" "INPUT_TEXT='$sentence'")
${command[#]}
I got the same output as the first script. I finally got what I wanted with:
result=$(curl http://localhost:59125/process -d INPUT_TEXT="${sentence}")
echo $result
I'm still unsure as to what the distinction is. In the first two cases, when I echoed out the contents of command, I get exactly what I input from the interactive Bash prompt, which had worked fine. What caused the difference?
The following will work:
command=("curl" "http://localhost:59125/process"
"-d" "INPUT_TEXT=$sentence")
"${command[#]}"
That has two changes from yours:
I removed the incorrect quotes around $sentence since you don't want to send quotes to the server (as far as I can see).
I put double-quotes around the use of "${command[#]}". Without the double quotes, the array's elements are concatenated with spaces between them and then the result is word-split. With double quotes, the individual array elements are used as individual words.
The second point is well-explained in the bash FAQ and a bunch of SO answers dealing with quotes.
The important thing to understand is that quotes only quote when a command is parsed. A quote which is a character in a variable is just a character; it is not reinterpreted when the value of the variable expanded. Whitespace in the variable is used for word-splitting if the variable expansion is unquoted; the fact that the whitespace was quoted in the the command which defined the variable is completely irrelevant. In this sense, bash is just the same as any other programming language.

Passing quotes and other special characters literally through bash and ssh

I am trying to run an SSH command that will invoke a script on a remote machine that writes some Lua code to a file.
I have this script command that executes under bash:
ssh bob writelua.sh '{version=1,{["foo"]=17}}'
And writelua.sh looks like this:
echo "return $1" > bar.lua
The end result, however, is that bar.lua has the content:
return version=1
I had thought that single quotes prevented all interpretation. How can I edit the scripts and escaping to pass the raw Lua code through unharmed?
The single quotes prevent interpretation on the local host. The remote host sees the command line
writelua.sh {version=1,{["foo"]=17}}
which is subject to brace expansion. You need a second set of quotes so that the first set of single quotes is passed through to the remote host.
ssh bob writelua.sh "'{version=1,{[\"foo\"]=17}}'"
As you can see, the quotes start to get unwieldy. A better solution is to simply copy a script containing
writelua.sh '{version=1,{["foo"]=17}}'
to the remote host and execute that remotely.
An example using the $'...' quotes:
ssh bob writelua.sh $'{version=1,{[\'foo\']=17}}'
Use heredoc and avoid all the excessive quoting:
ssh -T bob << \EOF
writelua.sh '{version=1,{["foo"]=17}}'
EOF
This will send raw script to remote host and it will get interpreted on the remote host itself.
When it gets too complex, particularly with lots of escaping, I prefer generating the command on a temporary script and execute it locally or remotely via SSH as required.
But there's an alternative: using echo to store the command in a variable and taking advantage of three things:
Single quotes don't do variable expansion and allow double quotes, so you can include something like "$myvar" without escaping $ or "
Double quotes allow variable expansion and single quotes, which means you can include something like animals='all'; echo love $animals to have $animals replaced by its value, and without escaping the '
Strings of both types, i.e. enclosed by single quotes or double quotes, can be concatenated simply by putting them together.
As an example, if I want something like this executed on a remote machine:
source /my-env.sh; perl -MMYLIB::DB -e 'my $t=db_list("name", 1553786458); print "#$t"'
But instead of 1553786458 I want to pass the value from a local variable:
now=`date +%s`
We could have this:
get_list=`echo 'source /my-env.sh; perl -MMYLIB::DB -e' "'my " '$t=db_list("name", ' "$now" '); print "#$t"' "'"`
You can see that single and double quotes are alternated, so we din't have to do any escaping! They don't need to be separated by spaces, but it improves readability and won't affect the result in this case.
And now we can execute:
ssh user#host $get_list
There's still no guarantee that this approach will always work, so once you've built your command, the safest bet would be to copy it over in a file.
If you can use Perl...
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new("bob");
$ssh->system('writelua.sh', '{version=1,{["foo"]=17}}')
or die $ssh->error;
Net::OpenSSH takes care of quoting everything for you.

Resources