4

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:

1.2.3.4:abcdefg
2.3.4.5:abc$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.

#!/bin/bash

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" }
EOD
)
    done < input.txt
fi

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

spawn ssh [email protected]

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

1
  • take a look at sexpect with which you can write Expect scripts with shell code only. Commented Aug 15, 2018 at 2:26

3 Answers 3

9

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

cat << 'EOD'
...
EOD

For example this file:

cat <<EOF
test\$literal
var$(echo this)
EOF

produces this result:

test$literal
varthis
4
  • 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. Commented Aug 14, 2018 at 20:40
  • Can you give an example? Sorry am not following. Commented Aug 14, 2018 at 21:17
  • I added an example. Commented Aug 14, 2018 at 21:28
  • @RalfFriedl perhaps you could also add an example of output when EOD is quoted Commented Jul 23, 2024 at 8:38
4

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
3

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"'
fooabcdefg

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"'
foo$bar[quux]

Or through the environment:

$ export var
$ expect -f - "$var" <<< 'set var $::env(var); send_user "$var\n"'
foo$bar[quux]

A bit more closer to what your code does:

#!/bin/bash
line='2.3.4.5:abc$def'
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"
EOF

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.

2

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.