I have a bash script which read an input file and using a heredoc to ssh to a server using expect, but am having issue escaping some special character in the input file. Here is what I have..

I have a file call input.txt which contain:$def

I have a bash script which look like this. It works fine if the password doesn't contain the character '$' but blow up when the password contain '$' as it treat the part after $ as a variable.


if [ -e "input.txt" ]; then
    while read i; do

/usr/bin/expect <(cat << EOD
set timeout 15
spawn ssh "user@$(echo $i | cut -d: -f 1)"

expect "yes/no" {
    send "yes\r"
    expect "Password:" { send "$(echo $i | cut -d: -f 2)\r" }
} "Password:" { send "$(echo $i | cut -d: -f 2)\r" }
expect -re "day: $" { send "\r" }
expect ":" { send "\r" }
expect -re "# $" { send "date\r" }
expect -re "# $" { send "exit\r" }
    done < input.txt

I got the following error when I run this and hit the second set of IP.

spawn ssh user@

Unauthorized use of this system is prohibited and may be prosecuted
to the fullest extent of the law. By using this system, you implicitly
agree to monitoring by system management and law enforcement authorities.
If you do not agree with these terms, DISCONNECT NOW.

Password: can't read "def": no such variable
    while executing
"send "abc$def\r" "
    invoked from within
"expect "yes/no" {
    send "yes\r"
    expect "Password:" { send "abc$def\r" }
} "Password:" { send "abc$def\r" }"
    (file "/dev/fd/63" line 5)

Anyone have any idea? I tried single quote inside double quote and everything I could think of but still can't get it to work. Thanks in advance

  • take a look at sexpect with which you can write Expect scripts with shell code only. – pynexj Aug 15 '18 at 2:26

Your issue isn't with the shell, or the here doc, but with Expect taking the $def as a variable reference, and trying to expand it.

Here, bar is a variable set within the Expect and expanded there (this is single-quoted, the shell doesn't expand $bar):

$ expect -f - <<< 'set bar abcdefg; send_user "foo$bar\n"'

This gives the same error you saw (the expect script is double-quoted now, so $var does expand in the shell, unlike in the previous):

$ var='foo$bar'; expect -f - <<< "send_user \"$var\n\""
can't read "bar": no such variable
    while executing
"send_user 'foo$bar\n'"

You'd need to escape the $, but then you'd need to escape at least [ too, since it's also special in Expect, or rather in Tcl which Expect is built on. Maybe there are others too, I'm not sure...

It might be easier to pass the value through a command line argument:

$ var='foo$bar[quux]'
$ expect -f - "$var" <<< 'set var [lindex $argv 0]; send_user "$var\n"'

Or through the environment:

$ export var
$ expect -f - "$var" <<< 'set var $::env(var); send_user "$var\n"'

A bit more closer to what your code does:

IFS=: read host pw <<< "$line"
export host pw

expect -f - << 'EOF'
set host $::env(host)
set pw $::env(pw)
send_user "host is '$host' pw is '$pw'\n"

With the variables passed through the environment, you can make the here-doc quoted so the shell doesn't expand the $s, and use the Expect variables $host and $pw where needed.

| improve this answer | |

The problems come from mixing shell code and expect code. This job is simple enough to code in expect only:

#!/usr/bin/expect -f
set timeout 15
set fh [open "input.txt" r]
while {[gets $fh line] != -1} {
    # using a regular expression allows a colon in the password.
    regexp {^([^:]+):(.*)} $line -> host passwd

    spawn ssh user@$host
    expect {
        "yes/no"    { send "yes\r"; exp_continue }
        "Password:" { send "$passwd\r" }
    expect -re "day: $" { send "\r" }
    expect ":"          { send "\r" }
    expect -re "# $"    { send "date\r" }
    expect -re "# $"    { send "exit\r" }
    expect eof
close $fh
| improve this answer | |

If you don't want any variables expanded inside the here doc, quote the EOD:

cat << 'EOD'

For example this file:

cat <<EOF
var$(echo this)

produces this result:

| improve this answer | |
  • if I quote the heredoc then it wouldn't work at all because I do use variable to plug in the IP and password I read from the input.txt file. – quan3t Aug 14 '18 at 20:40
  • Can you give an example? Sorry am not following. – quan3t Aug 14 '18 at 21:17
  • I added an example. – RalfFriedl Aug 14 '18 at 21:28

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.