Search code examples
bashshellsshautomationexpect

Why does my Expect script only echo the command not running?


I'm trying to automate some ssh process. I have my Expect code. But my Expect code only echos/prints out the command. It doesn't actually run the command.

#!/usr/bin/expect -f

set timeout 10
set usrnm "aaaaaa"
set pwd "pppppp"
set addr1 "xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
set addr2 "xxx.xxxx.xxxx.com"

spawn ssh $usrnm@$addr1

expect {

    "(yes/no)?" {send "yes\r";exp_continue}

    "password: " {send  "$pwd\r"}

}


expect "*#"
send "ssh $usrnm@$addr2\r"

expect {

    "(yes/no)?" {send "yes\r";exp_continue}

    "password:" {send  "$pwd\r"}

}

expect "*#"

send "cd /tmp/myself/folder\r"

expect "*#"

send "./run_engine.sh test.py\r"

expect eof

#interact

So if I do

expect my_expect.exp

it just prints the command:

spawn ssh aaaaaa@xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no (10s later) ssh aaaaa@xxx.xxxx.xxxx.com (10s later) cd /tmp/amz337/COAFALV (10s later) ./run_engine.sh test.py (exit)

What's wrong with my script?


Solution

  • Because Tcl (and thus Expect) does not change the word boundaries when variables get substituted. You are trying to log into the host named exactly:

    xxx.cloud.xxx.com -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
    

    spaces and all.

    Logically, it does not make sense to put ssh options into a variable that holds the address. May I suggest:

    set addr1 "xxx.cloud.xxx.com"
    set addr2 "xxx.xxxx.xxxx.com"
    set ssh_opts($addr1) {-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no}
    set ssh_opts($addr2) {}
    

    Then

    spawn ssh {*}$ssh_opts($addr1) $usrnm@$addr1
    

    The {*} syntax is Tcl's "splat" operator that splits a word with spaces into the individual words. See https://tcl.tk/man/tcl8.6/TclCmd/Tcl.htm rule #5.

    Later, when you connect to the second machine, you're interpolating into a string, so the splat is not necessary:

    send "ssh $ssh_opts($addr2) $usrnm@$addr2\r"
    

    You might want to catch timeout events and abort the script:

    expect {
        timeout      {error "timed-out connecting to $addr1"}
        "(yes/no)?"  {send "yes\r"; exp_continue}
        "password: " {send  "$pwd\r"}
    }
    

    At the end of your script, after the run_engine script completes, you're still connected to addr2, so expect eof will not actually detect EOF on the spawned process. You'll timeout after 10 seconds and the Expect process will exit. For tidiness, you should:

    send "./run_engine.sh test.py\r"
    expect "*#"
    send "exit\r"
    # This prompt is from addr1
    expect "*#"
    send "exit\r"
    # _Now_ the spawned ssh process will end
    expect eof
    

    If you think the run_engine script will take longer than 10 seconds, you should adjust the timeout variable before sending that command.

    Also, while developing an Expect script, you should turn on debugging:

    exp_internal 1
    

    That will show you what's going on behind the scenes, especially when it comes to seeing if your patterns are matching.