Shell variables in command line usage

1,939 views
Skip to first unread message

Dag Wieers

unread,
Mar 23, 2012, 9:23:31 AM3/23/12
to ansible...@googlegroups.com
Hi,

Being used to tools like ssh or shmux, I was wondering why the examples
for command line usage are more complex wrt. escaping special characters.

[root@moria ansible]# ssh localhost 'echo $TERM'
dumb

I would expect the following to work:

[root@moria ansible]# ansible all -a 'echo $TERM'
127.0.0.1 | success | rc=0 >>
$TERM

But as the example shows, you need double escaping.

[root@moria ansible]# ansible all -a 'echo \$TERM'
127.0.0.1 | success | rc=0 >>
$TERM

[root@moria ansible]# ansible all -a "echo \\$TERM"
127.0.0.1 | success | rc=0 >>
xterm

This makes using the command line functionality much harder than one would
expect in the first place. What's more escaping does not even work with
single quotes, which is what one would use. Having to use double quotes
requires one to have double escaping.

Where is this coming from ? And can this be improved ?

Should I open an issue for this ?
--
-- dag wieers, d...@wieers.com, http://dag.wieers.com/
-- dagit linux solutions, in...@dagit.net, http://dagit.net/

[Any errors in spelling, tact or fact are transmission errors]

Michael DeHaan

unread,
Mar 23, 2012, 9:28:15 AM3/23/12
to ansible...@googlegroups.com
Don't open an issue, it's entirely a shell thing.

That example was about echoing the remote's term, not my local term on the remote machine.

The only thing in play there is bash, there is nothing from Ansible requiring it … so it may be the example just needs correcting IF
that isn't required for you.   But I think it is.   

(Again though, there is nothing in Ansible that is trying to replace that dollar sign, because TERM isn't set to a variable, and it knows it should not mess
with it.)

Michael DeHaan

unread,
Mar 23, 2012, 9:43:06 AM3/23/12
to ansible...@googlegroups.com
Yeah, the issue was the example should have used single quotes versus double quotes.   Not an Ansible thing.

If you want to file an issue against the docs project, that's good.

Dag Wieers

unread,
Mar 23, 2012, 9:44:19 AM3/23/12
to ansible...@googlegroups.com
On Fri, 23 Mar 2012, Michael DeHaan wrote:

> Don't open an issue, it's entirely a shell thing.
>
> That example was about echoing the remote's term, not my local term on the remote machine.

Of course. But that's not what is happening in the example:

[root@moria ansible]# echo "$TERM"
xterm
[root@moria ansible]# echo "\$TERM"
$TERM
[root@moria ansible]# echo "\\$TERM"
\xterm

So if you do the example as provided in http://ansible.github.com/examples.html

ansible raleigh -m shell -a "echo \\$TERM"

You are sending "echo \xterm" to the remote bash. Which does:

[root@moria ansible]# echo "echo \\$TERM" | bash
xterm

Now, the whole problem here obviously is that the example makes it very
confusing, if we would use a different variable that is unique (e.g. $$)
you'll see what I mean

[root@moria ansible]# echo "$$"
5334
[root@moria ansible]# echo "\$$"
$$
[root@moria ansible]# echo "\\$$"
\5334

In fact what you would expect to work is this:

[root@moria ansible]# echo 'echo $$' | bash
11531
or
[root@moria ansible]# echo "echo \$$" | bash
11534

instead of

[root@moria ansible]# echo "echo \\$$" | bash
5334

I hope this makes it more clear what I meant. So that's why I am convinced
something is not being processed as it should.

Dag Wieers

unread,
Mar 23, 2012, 9:48:41 AM3/23/12
to ansible...@googlegroups.com
On Fri, 23 Mar 2012, Dag Wieers wrote:

> On Fri, 23 Mar 2012, Michael DeHaan wrote:
>
>> Don't open an issue, it's entirely a shell thing.
>>
>> That example was about echoing the remote's term, not my local term on the
>> remote machine.
>
> Of course. But that's not what is happening in the example:

-snip-

> I hope this makes it more clear what I meant. So that's why I am convinced
> something is not being processed as it should.

What I think is happening (and why using single quotes or single escaping
does not work) is that paramiko is escaping the commandline. I wonder if
this is configurable ?

I should really dig into the code now that I know this is a real issue.

Sorry for the noise...

Michael DeHaan

unread,
Mar 23, 2012, 9:51:33 AM3/23/12
to ansible...@googlegroups.com
There is some escaping going on in Runner with regards to the setup module, in particular.

It can probably be refined.

If you'd like to play with it, be my guest… 

(Much more likely to be args processing prior to paramiko)

--Michael

Michael DeHaan

unread,
Mar 23, 2012, 9:53:55 AM3/23/12
to ansible...@googlegroups.com
Sorry for the spam -- here's the crux of it.   It should not be hard to fix at all.

The system tends to treat module_args as an array in places.   It's a string.   There is no reason anything should see it as anything BUT a string (the modules like strings), so removing the things that treat args as an array is probably the best way to go.

Treat them as a string all the way through the stack.

There are some places where we have to be careful, like when replacing variables that might contain spaces, so that they DO get quoted.   This can happen
when assigning a long string of text for a motd message variable, for instance.

--MPD

Dag Wieers

unread,
Mar 23, 2012, 10:12:48 AM3/23/12
to ansible...@googlegroups.com
On Fri, 23 Mar 2012, Michael DeHaan wrote:

> Sorry for the spam -- here's the crux of it. It should not be hard to
> fix at all.
>
> The system tends to treat module_args as an array in places. It's a
> string. There is no reason anything should see it as anything BUT a
> string (the modules like strings), so removing the things that treat
> args as an array is probably the best way to go.

The only piece I can find that interprets the string might be
shlex.split(), however using it as a string still does not work as
expected.

While it does work with a basic paramiko script.

----
#!/usr/bin/python

import sys
import paramiko

client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect('localhost')
stdin, stdout, stderr = client.exec_command(sys.argv[1])
print stdout.read()
----

[root@moria ansible]# python test.py 'echo $$'
31199

[root@moria ansible]# python test.py 'echo $$'
31218

[root@moria ansible]# python test.py "echo \$$"
31273


> Treat them as a string all the way through the stack.

I cannot find what's causing the behaviour we see in the most simple case
though.

PS I did fix the double quoting where ansible runs logger

seth vidal

unread,
Mar 23, 2012, 10:14:50 AM3/23/12
to ansible...@googlegroups.com, d...@wieers.com

I suspect this is due to the args being passed in as a file to the
commands running them. This was done b/c otherwise we would end up with
shell expansion from where you've run them and then shell expansion
again when exec_command ran them.

-sv

Michael DeHaan

unread,
Mar 23, 2012, 10:26:04 AM3/23/12
to ansible...@googlegroups.com, d...@wieers.com
This in /usr/bin/ansible is what I'm saying should be unnecessary, because strings are tolerated:

            module_args=shlex.split(options.module_args),

(The argsfile stuff to transfer the data should not be hurting anything in theory…. the code should not have to split anything at all until the module picks it up on the other side)

This results in the code trying to make the array back into a string and that is not necessary.

Dag Wieers

unread,
Mar 23, 2012, 10:31:03 AM3/23/12
to ansible...@googlegroups.com
On Fri, 23 Mar 2012, Michael DeHaan wrote:

> This in /usr/bin/ansible is what I'm saying should be unnecessary, because strings are tolerated:
>
> module_args=shlex.split(options.module_args),
>
> (The argsfile stuff to transfer the data should not be hurting anything in theory…. the code should not have to split anything at all until the module picks it up on the other side)
>
> This results in the code trying to make the array back into a string and that is not necessary.

I doubt the above is the culprit. The command module is using sys.argv
directly. This was what confused me until Seth hinted this.

However, I noticed that the command module is running Popen in a shell,
and I think that's being a problem (this together potentially with the
shlex.split in the command module).

But what I lack to understand is how Popen integrates with Paramiko. I was
made to believe we were using exec_command() for remote commands, but
that's apparently not the case :-/

seth vidal

unread,
Mar 23, 2012, 10:32:58 AM3/23/12
to ansible...@googlegroups.com, d...@wieers.com
On Fri, 23 Mar 2012 15:31:03 +0100 (CET)
Dag Wieers <d...@wieers.com> wrote:

> On Fri, 23 Mar 2012, Michael DeHaan wrote:
>
> > This in /usr/bin/ansible is what I'm saying should be unnecessary,
> > because strings are tolerated:
> >
> > module_args=shlex.split(options.module_args),
> >
> > (The argsfile stuff to transfer the data should not be hurting
> > anything in theory…. the code should not have to split anything at
> > all until the module picks it up on the other side)
> >
> > This results in the code trying to make the array back into a
> > string and that is not necessary.
>
> I doubt the above is the culprit. The command module is using
> sys.argv directly. This was what confused me until Seth hinted this.
>
> However, I noticed that the command module is running Popen in a
> shell, and I think that's being a problem (this together potentially
> with the shlex.split in the command module).
>
> But what I lack to understand is how Popen integrates with Paramiko.
> I was made to believe we were using exec_command() for remote
> commands, but that's apparently not the case :-/
>


So here is what ansible does.

it sends over the module (in this case command) with a set of arguments.

Then it exec_command()s that module with those arguments on the remote
machine.

if you look in your syslog on the remote machine you will see what is
being run.

-sv

Michael DeHaan

unread,
Mar 23, 2012, 10:41:56 AM3/23/12
to ansible...@googlegroups.com, d...@wieers.com
Let's take the debugging discussion off list (or use IRC) and just include you/me/Seth.

I doubt too many people are interested in it low level details, just in making sure that shell vars are happy for those that want to use them :)

Dag Wieers

unread,
Mar 23, 2012, 10:42:24 AM3/23/12
to ansible...@googlegroups.com
On Fri, 23 Mar 2012, seth vidal wrote:

> On Fri, 23 Mar 2012 15:31:03 +0100 (CET)
> Dag Wieers <d...@wieers.com> wrote:
>
>> But what I lack to understand is how Popen integrates with Paramiko.
>> I was made to believe we were using exec_command() for remote
>> commands, but that's apparently not the case :-/
>
> So here is what ansible does.

Thanks !

> it sends over the module (in this case command) with a set of arguments.
>
> Then it exec_command()s that module with those arguments on the remote
> machine.
>
> if you look in your syslog on the remote machine you will see what is
> being run.

So the example piece of code from command fails even locally:

----
#!/usr/bin/python

import sys
import subprocess
import traceback

try:
cmd = subprocess.Popen(sys.argv[1].split(), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
except (OSError, IOError), e:
print e
sys.exit(1)
except:
print traceback.format_exc()
sys.exit(1)

print out.strip()
----

And you get:

[root@moria ansible]# python test2.py 'echo $$'
$$

This is where it happens.

If you do:

----
#!/usr/bin/python

import sys
import subprocess
import traceback

try:
cmd = subprocess.Popen([ 'echo $$' ], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
except (OSError, IOError), e:
print e
sys.exit(1)
except:
print traceback.format_exc()
sys.exit(1)

print out.strip()
----

Then it magically works. Solution:

- Don't split, but put it in a list
- use shell=True

seth vidal

unread,
Mar 23, 2012, 10:46:01 AM3/23/12
to ansible...@googlegroups.com, d...@wieers.com
On Fri, 23 Mar 2012 15:42:24 +0100 (CET)
Dag Wieers <d...@wieers.com> wrote:

so two things:
1. If you specify the module name to ansible as 'shell' I believe it
says shell=True in there and does what you want.
2. the string vs list thing is involved too.

-sv

seth vidal

unread,
Mar 23, 2012, 10:46:38 AM3/23/12
to ansible...@googlegroups.com, michael...@gmail.com, d...@wieers.com
On Fri, 23 Mar 2012 10:41:56 -0400
Michael DeHaan <michael...@gmail.com> wrote:

> Let's take the debugging discussion off list (or use IRC) and just
> include you/me/Seth.
>
> I doubt too many people are interested in it low level details, just
> in making sure that shell vars are happy for those that want to use
> them :)


I think that's exactly what a list is for - so people can find this
discussion later when they search for a question.

archiving for the future!! :)
-sv

Michael DeHaan

unread,
Mar 23, 2012, 12:27:34 PM3/23/12
to ansible...@googlegroups.com, d...@wieers.com
Seems like this is resolved by using the shell module instead of the command module, combined with single quoting versus double quoting when passing arguments for the CLI.

(I still am going to make some cleanup around string processing.)

--Michael
Reply all
Reply to author
Forward
0 new messages