Expect within a Bash script

Pkp picture Pkp · Apr 7, 2011 · Viewed 48.3k times · Source

I wrote a Bash script with Expect within, to connect to a terminal server and clear lines.

I am unable to figure out the error I am getting, as in I have given all the braces necessary. I also do not understanding the couldn't read file "line": no such file or directory error. How can I fix this?

My script:

#!/bin/bash
VAR=$(expect -c "
spawn telnet 1.1.1.1
expect {
       "Password:" { send "password\r" ; exp_continue}
       "Prompt>" { send "en\r" ; exp_continue}
       "Password:" { send "password\r" ; exp_continue}
       "Prompt#" {send "clea line 10\r" ; exp_continue}
       "[confirm]" {send "Y\r" ; exp_continue}
       "Prompt#" {send "clea line 11\r" ; exp_continue}
       "[confirm]" {send "Y\r" ; exp_continue}
       "Prompt#" {send "exit\r" }
    }
")

echo $VAR

Its output:

missing close-brace
    while executing
"expect"
couldn't read file "line": no such file or directory

Answer

Chris Johnsen picture Chris Johnsen · Apr 7, 2011

The first problem is that the shell does not interpret nested double quotes as you might like. The easiest way to fix this is to put the Expect program in single quotes. This will be sufficient as long as there are no single quotes in the Expect program itself.

The next problem you will run into is that having all the patterns and actions in a single expect command will process them in parallel. What is actually happens is that the first Password: pattern will match any time it sees that string (i.e. even for the admin password the second time around). This will be a problem if the two passwords need to be different. At a minimum, identical patterns will need to go into separate expect commands so that they can be executed sequentially. This problem also affects the Prompt# pattern where you look for it three times and want to send three different responses.

Later, you will get an error after you send the first clear command. Expect interprets square brackets inside double quotes in a way that is similar to how shells interpret $() or `` (i.e. command substitution). You will see an error like this:

invalid command name "confirm"
    while executing
"confirm"
    invoked from within
"expect {  
⋮

It is trying to run confirm as a Tcl (or Expect) command. You can use curly brackets ({}) to prevent Tcl from making this interpretation. Furthermore, expect patterns are treated as “glob” expressions by default (i.e. like shell wildcards), so even if you write {[confirm]} as the pattern, it will still not be used for an exact string match (it would match any single character c, o, n, f, i, r, or m). You must use the -ex flag to mark the pattern for exact matching.

Fix these issues, drop some of the unnecessary quoting, and you might end up with something like this:

#!/bin/sh
VAR=$(expect -c '
  proc abort {} {
    puts "Timeout or EOF\n"
    exit 1
  }
  spawn telnet 1.1.1.1
  expect {
    Password:        { send "password1\r" }
    default          abort
  }
  expect {
    Prompt>          { send "en\r"; exp_continue }
    Password:        { send "password2\r" }
    default          abort
  }
  expect {
    Prompt#          { send "clea line 10\r"; exp_continue }
    -ex {[confirm]}  { send "Y\r" }
    default          abort
  }
  expect {
    Prompt#          { send "clea line 11\r"; exp_continue }
    -ex {[confirm]}  { send "Y\r" }
    default          abort
  }
  expect {
    Prompt#          { send "exit\r"; exp_continue }
    timeout          abort
    eof
  }
  puts "Finished OK\n"
')

echo "$VAR"