Question

I am trying to use a FOR loop to iterate over IP addresses (in a bash array), logs in, runs a script and then exits. The array is called ${INSTANCE_IPS[@]}. The following code doesn't work though, as expect doesn't seem to be able to accept the variable $instance.

for instance in ${INSTANCE_IPS[@]}
  do
  echo $instance
  /usr/bin/expect -c '
  spawn ssh root@$instance;
  expect "?assword: ";
  send "<password>\r";
  expect "# ";
  send ". /usr/local/bin/bootstrap.sh\r";
  expect "# ";
  send "exit\r" '
done

However, expect complains with:

can't read "instance": no such variable
    while executing
"spawn ssh root@$instance"

There is another question on stackoverflow located here, that uses environmental variables to achieve this, however it doesn't allow me to iterate through different IP addresses like I can in an array.

Any help is appreciated.

Cheers

Was it helpful?

Solution

The problem is with quoting. Single quotes surrounding the whole block don't let Bash expand variables ($instance).

You need to switch to double quotes. But then, double quotes inside double quotes are not allowed (unless you escape them), so we are better off using single quotes with expect strings.

Try instead:

for instance in ${INSTANCE_IPS[@]}
  do
  echo $instance
  /usr/bin/expect -c "
  spawn ssh root@$instance;
  expect '?assword: ';
  send '<password>\r';
  expect '# ';
  send '. /usr/local/bin/bootstrap.sh\r';
  expect '# ';
  send 'exit\r' "
done

OTHER TIPS

for instance in ${INSTANCE_IPS[&]} ;  do
    echo $instance
    /usr/bin/expect -c '
    spawn ssh root@'$instance' "/usr/local/bin/bootstrap.sh"
    expect "password:"
    send "<password>\r"
    expect eof'
done

From the ssh man page:

If command is specified, it is executed on the remote host instead of a login shell.

Specifying a command means expect doesn't have to wait for # to execute your program, then wait for another # just to send the command exit. Instead, when you specify a command to ssh, it executes that command; it exits when done; and then ssh automatically closes the connection.

Alternately, put the value in the environment and expect can find it there

for instance in ${INSTANCE_IPS[&]} ;  do
    echo $instance
    the_host=$instance /usr/bin/expect -c '
        spawn ssh root@$env(the_host) ...

Old thread, and one of many, but I've been working on expect for several days. For anyone who comes across this, I belive I've found a doable solution to the problem of passing bash variables inside an expect -c script:

#!/usr/bin/env bash
password="TopSecret"

read -d '' exp << EOF
set user "John Doe"
puts "\$user"
puts "$password"
EOF

expect -c "$exp"

Please note that escaping quotations are typically a cited issue (as @Roberto Reale stated above), which I've solved using a heredoc EOF method, before passing the bash-variable-evaluated string to expect -c. In contrast to escaping quotes, all native expect variables will need to be escaped with \$ (I'm not here to solve all first-world problems--my afternoon schedule is slightly crammed), but this should greatly simplify the problem with little effort. Let me know if you find any issues with this proof of concept.

tl;tr: Been creating an [expect] daemon script with user authentication and just figured this out after I spent a whole day creating separated bash/expect scripts, encrypting my prompted password (via bash) with a different /dev/random salt each iteration, saving the encrypted password to a temp file and passing the salt to the expect script (highly discouraging anyone from easily discovering the password via ps, but not preventative since the expect script could be replaced). Now I should be able to effectively keep it in memory instead.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top