bashshellsshexpect

Bash using Expect script gives error for ssh call


In a bash script I have an expect segment where it sends commands to a machine via ssh. I have the bash -c there as I need to set environment variables in this command. But I'm getting an error:

extra characters after close-quote
    while executing
"spawn -noecho ssh -t anon@machine  'bash -c "cd /tmp;"'
"

When I run that command manually ssh -t anon@machine 'bash -c "cd /tmp;"' it works fine. What is wrong here?

   ssh_param="-t ${scp_user}${_machine}  'bash -c \"cd /tmp;\"'"

    /usr/bin/expect <<-EOF1
    set timeout 360
    spawn -noecho ssh ${ssh_param}
    expect {
        "password:" { 
             send "${_pass}\r" 
         }
         eof { exit }
    }
    expect {
         eof { exit }
    }
EOF1

Also if I set ssh_param as ssh_param="-t ${scp_user}${_machine} \"cd /tmp;\"" this works, so why does bash -c ... cause it to fail?


Solution

  • After bash does its parameter substitution on the heredoc, expect is run with

    set timeout 360
    spawn -noecho ssh -t XXXYYY 'bash -c "cd /tmp;"'
    expect {
        "password:" { 
             send "ZZZ\r" 
         }
         eof { exit }
    }
    expect {
         eof { exit }
    }
    

    expect scripts are written in the tcl language, which basically works by splitting up a line into words and treating the first word as a command to run with the rest of the words as arguments after doing substitutions. Kind of like shell that way, but with saner rules. The 12 rules for parsing tcl are called the Dodekalogue.

    Single quotes are not used to group multiple words into one - only double quotes, [] (Which indicates a command and arguments to evaluate), and {} (Which don't substitute or evaluate anything in them). So the line

    spawn -noecho ssh -t XXXYYY 'bash -c "cd /tmp;"'
    

    is split up into spawn, -noecho, ssh, -t, XXXYYY, 'bash, -c and "cd /tmp;"'. The single quote at the end after the closing double quote of the last word is an error. Normally in tcl you'd just wrap it in curly braces instead (Or double quotes but then you have to escape the nested ones):

    spawn -noecho ssh -t XXXYYY {bash -c "cd /tmp;"}
    

    which means the shell variable you set needs to look something like

    ssh_param="-t ${scp_user}${_machine} {bash -c \"cd /tmp;\"}"
    

    Writing the entire thing in tcl/expect instead of shell that builds expect scripts to execute will make life easier, as you don't have to switch between different quoting styles, worry about shell parameter expansion causing invalid tcl to be generated like this example, etc.