bashexpect

Prevent expect script from looking for a variable


I have the following function. It tests for a SFTP server connection. It does what it's supposed to, but it has one edge-case that's causing me a headache. When the variable SFTP_SERVER_PASSWORD contains a dollar-sign it won't work because the expect script thinks it has to search for a variable.

function test_sftp_connection() {
expect << EOF
log_user 1
spawn -noecho sftp -P $SFTP_SERVER_PORTNUMBER $SFTP_SERVER_USERNAME@$SFTP_SERVER_HOSTNAME
expect "password:"
send "$SFTP_SERVER_PASSWORD\r"
expect {
    "sftp>" {
      send "exit\r"
      expect eof
      exit 0
    }
    timeout {
      exit 1
    }
  }
EOF
}

I tried various solutions:

  1. send $SFTP_SERVER_PASSWORD"\r"
  2. send "$SFTP_SERVER_PASSWORD\r"
  3. send "'$SFTP_SERVER_PASSWORD'\r"

But they all end up with the same error:
'can't read "[few letters after dollar-sign in password]": no such variable'.

I guess this is hard because it is using a Bash variable in an expect script. But I still think this should be solvable. Does anyone have any ideas on how to solve this?


Solution

  • From the viewpoint of the Tcl language:

    You have to escape the $ if you want to send a $ literally:

     send "\$your password\r"
    

    However your case is complicated in that you pass to the Tcl program a string, which undergoes parameter expansion by the shell. This means that if you have a shell variable SFTP_SERVER_PASSWORD, and the variable contains a $, there would be no escaping.

    One solution would be to replace every $ inside the shell variable by the string \$. However in my opinion, this is cumbersome and error prone. A similar possibility would be to place the shell variable into the environment, before calling Tcl, i.e.

    export SFTP_SERVER_PASSWORD
    

    and then in your Tcl script do a

    send \$env(SFTP_SERVER_PASSWORD)\r
    

    This also fixes the danger of code injection. In your original approach, a password containing inside the string [foo] , would try to execute the Tcl command foo, and if the string between the brackets happens to be a valid Tcl function, it could execute arbitrary Tcl code under the hood.

    Note that the backslash in front of the $env has the purpose to tell the shell NOT to interpret env as shell variable. This is necessary because you have the complete Tcl program included in a double quoted context.

    If you find this escaping ugly (as I would), you can ask the shell to completely keep its fingers from the Tcl text. Right now, you are "using" the shell to expand the variables in the spawn command. This is fine, since these variables won't contain a white space or a character which is special to Tcl, but still I would decouple here Tcl and shell completely:

    If you place those variables into the environment as well and refer to then via $env(....) inside Tcl, you can put your whole Tcl program into single quoted context (or perhaps into its own file, with a #!/usr/bin/expect line on top and don't have to worry about shell issues anymore.