use expect to spawn command with arguments containing spaces

Richard picture Richard · Sep 20, 2012 · Viewed 46.5k times · Source

I want to use expect to run a simple command cat /tmp/id_rsa.pub over ssh.

In a shell, I can run this wo problem, (with manually put in the password)

ssh root@localhost 'cat /tmp/id_rsa.pub'

I want to automate this with expect. My expect script is,

#!/usr/bin/expect
eval spawn ssh root@localhost 'cat /tmp/id_rsa.pub'
expect "password:"
send "123456"
expect eof

It throws error bash: cat /tmp/id_rsa.pub: no such file or directory. it looks very strange to me. What could be the possible cause?

Edit: after some testing, I find this is common, not only in the case of cat. If the argument to spawned command is with space (even if it's in the quotes), it will have problem. For example, replacing cat /tmp/id_rsa.pub with other commands with spaces, like

eval spawn ssh root@localhost 'which java'

it complains with bash: which java: command not found. But if replacing that with pwd, like

eval spawn ssh root@localhost 'pwd'

it work fine.

Answer

Johannes Kuhn picture Johannes Kuhn · Oct 16, 2013

Single quotes (') have no special meaning to Expect, unlike sh and other compatible shells.
This means that your statment

spawn ssh root@localhost 'cat /tmp/id_rsa.pub'

is parsed into the following words:

  • spawn
  • ssh
  • root@localhost
  • 'cat - not until the other single quote.
  • /tmp/id_rsa.pub'

The usage in sh is to group this to a single argument. In Tcl you could either use double quotes (") or curly brackets ({}). Inside double quotes, Tcl variables will be substituted, while the content inside {} is passed without any substitution1.

tl;dr The Expect/Tcl equivalent of sh's ' are {}.

1 A \ before a newline will still be substitued.