Mike
Mike

Reputation: 31

Unable to SSH a script of bash commands via expect

I'm attempting to push a single set of commands to multiple remote hosts. I can use the following on an individual basis:
ssh user@remoteHost "bash -s" <./commands.sh
I can put this into a loop, but then I'm stuck typing in the password n number of times. Disabling the password prompts in the SSH config files is not an option for me. I've attempted to use expect within a loop, but I'm unable to get it working.

#!/bin/bash  
HOSTS="host1 host2"  
read -sp "Password: " PASSWORD  
for HOST in $HOSTS; do  
    expect -c "  
    spawn /usr/bin/ssh user@$HOST "bash -s" <./commands.sh  
    expect_before {
        "*yes/no*" {send "yes"\r;exp_continue}}
    expect {
        "*Password*" {send $PASSWORD\r;interact}}
    exit"
done

I get the following error:

spawn /usr/bin/ssh root@host1 bash  
expect: invalid option -- 's'  
usage: expect [-div] [-c cmds] [[-f] cmdfile] [args]  
spawn /usr/bin/ssh root@host2 bash  
expect: invalid option -- 's'  
usage: expect [-div] [-c cmds] [[-f] cmdfile] [args]  

Any ideas? It appears as though expect is trying to interpret the bash commands. I'm unsure how to stop this.

Upvotes: 2

Views: 1212

Answers (2)

glenn jackman
glenn jackman

Reputation: 247122

I'd suggest this:

#!/bin/bash  
hosts=(host1 host2)
read -sp "Password: " password  
for host in "${hosts[@]}"; do  
    env h="$host" p="$password" expect <<'END_EXPECT'
        spawn sh -c "/usr/bin/ssh user@$env(h) 'bash -s' <./commands.sh"
        expect {
            "*yes/no*"   {send "yes\r"; exp_continue}
            "*Password*" {send "$env(p)\r"}
        }
        interact
END_EXPECT
done

notes

  • uses lower case variable names: leave upper case varnames for the shell's use
  • uses a quoted heredoc to contain the expect code
    • that lets you use single and double quotes within expect without having to worry about quoting hell in the shell
  • uses env to pass shell variables to expect via the environment
  • simplifies your expect statement

The danger with using

expect -c " ...; expect "*Password*" ..."

is that the inner double quotes get matched with the outer quotes, and are removed by the shell. That leaves *Password* as a bare glob that the shell can expand based on the files in your current directory and the shell settings. For example, create a file named "The Password" (with a space) and you'll get an error.

Upvotes: 0

Mike
Mike

Reputation: 31

Solution:
replace
spawn /usr/bin/ssh user@$HOST "bash -s" <./commands.sh
with
spawn sh -c {ssh root@$HOST 'bash -ls' < /tmp/commands.sh}
Final Code:

#!/bin/bash

HOSTS="host1 host2"

read -sp "Password: " PASSWORD

for HOST in $HOSTS; do
        expect -c "
                spawn sh -c {ssh root@$HOST 'bash -ls' < /tmp/commands.sh}
                expect_before {
                "*yes/no*" {send "yes"\r;exp_continue}}
                expect {
                "*assword*" {send $PASSWORD\r;interact}}
                exit"
done

Upvotes: 1

Related Questions