I'm trying to write a bash script to reboot multiple machines via ssh that are all password protected. I have all the IPs listed in a .txt file and want the script to read the IPs from there. I've been messing with the expect command since my mac does not have sshpass. Is there a way to do this without sshpass??

I have this so far:


for server in 'testreboot.txt'; do
expect -c 'spawn ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 
administrator@$server "sudo shutdown -r now"; expect "Password:"; send 
"password\r"; interact'

In testreboot.txt I simply have the IP address listed of a test machine. Is that ok or do I need to assign it to a variable? I keep getting a variable error when I try to run it.

  • It might be time to start looking into Python. I really like paramiko for this sort of work. Also consider Ansible – Ben Nov 29 '18 at 1:07
  • take a look at sexpect with which you can write Expect scripts with shell code only. – pynexj Nov 29 '18 at 5:47

You have single quoted the string; this prevents the shell from interpolating $server. TCL (which is what expect code is written in) also uses the $server form to interpolate variables, but you have not set server ... anywhere in your TCL code. Hence the error, as the shell did not interpolate it nor did the TCL set it. One method would be to double quote the TCL expression which will allow the shell to interpolate in the shell $server variable:

$ server=example; expect -c 'puts $server'
can't read "server": no such variable
    while executing
"puts $server"
$ server=example; expect -c "puts $server"

However this will mean that any " in the TCL code will need to be escaped to protect them from messing with the shell interpolation which will probably get ugly real fast and will be hard to debug, especially as the commands get longer and more complicated.

$ server=example; expect -c "puts \"a server named '$server'\""
a server named 'example'

Another option would be to read the argument into a TCL variable, but, alas, the -c flag code of expect has no access to the arguments list in char *argv[] as the -c code is run before the arguments list is made available for use by TCL. Therefore to use $server as a TCL variable the script would need to be rewritten in TCL:

#!/usr/bin/env expect

set fh [open testreboot.txt r]

while {[gets $fh server] >= 0} {
    spawn -noecho ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 \
    expect -ex "Password:"
    send "Hunter2\r"
    expect -ex {$ }
    send "sudo shutdown -r now\r"
    expect -ex "Password:"
    send "Hunter2\r"

which will may need improvements for error handling, timeouts, matching the shell prompt better, etc.


It's not easy to embed Expect in shell scripts. I'd give you an example using the sexpect tool.

The code:

[STEP 101] # cat foo.xsh
while read host; do
    echo ==== $host ====
    export SEXPECT_SOCKFILE=~/tmp/host-$host-$$.sock

    sexpect spawn \
        ssh -o StrictHostKeyChecking=no \
            -o ConnectTimeout=5 \
            -o UserKnownHostsFile=/dev/null \
            -t \
            $user@$host bash --norc
    sexpect expect -i password:
    sexpect send -enter "$passwd"
    sexpect expect -re 'bash-[.0-9]+[#$] $'

    sexpect send -enter "sudo date"
    sexpect expect -i -re "password( for [^[:blank:]]+):"
    sexpect send -enter "$passwd"
    sexpect expect -re 'bash-[.0-9]+[#$] $'

    sexpect send -enter exit
    sexpect wait
done < hosts-file


[STEP 102] # cat hosts-file
[STEP 103] # bash foo.xsh
==== host1 ====
foo@host1's password:
bash-4.4$ sudo date
[sudo] password for foo:
Thu Nov 29 14:28:39 CST 2018
bash-4.4$ exit
Connection to host1 closed.
==== host2 ====
foo@host2's password:
bash-4.4$ sudo date
[sudo] password for foo:
Thu Nov 29 14:28:40 CST 2018
bash-4.4$ exit
Connection to host2 closed.
[STEP 104] #

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.