meso_2600
meso_2600

Reputation: 2136

expect fails when running proc inside proc

My script works fine (retrieves sftp prompt) when using one proc. But when I try to use proc inside proc, script gets stuck, and I do not know why.

Please do not refactor the code, that is not the point, I need to understand what is the issue here.

Working code:

proc sftp_connect {} {
  set times 0;
  set connection_retry 2
  set timeout 1;
  while { $times < $connection_retry } {
    spawn sftp ${SFTP_USER}@${SFTP_SERVER}
    expect {
      timeout { puts "Connection timeout"; exit 1}
      default {exit 2}
      "*assword:*" { 
        send "${SFTP_PASSWORD}\n";
        expect {
          "sftp>" { puts "Connected"; set times [ expr $times+1]; exp_continue}
        }
      }
    }
  }
  send "quit\r";
}

sftp_connect

Debug output:

expect: does "\r\nsftp> " (spawn_id exp5) match glob pattern "sftp>"? yes

But after moving send password into separate proc, expect does not retrieve sftp prompt anymore ("sftp>"):

proc sftp_send_password {} {
  send "${SFTP_PASSWORD}\n";
  expect {
    "sftp>" { puts "Connected"; set times [ expr $times+1]; exp_continue}
  }
}

proc sftp_connect {} {
  set times 0;
  set connection_retry 2
  set timeout 1;
  while { $times < $connection_retry } {
    spawn sftp ${SFTP_USER}@${SFTP_SERVER}
    expect {
      timeout { puts "Connection timeout"; exit 1}
      default {exit 2}
      "*assword:*" { sftp_send_password }
    }
  }
  send "quit\r";
}

sftp_connect

Debug output:

expect: does "" (spawn_id exp0) match glob pattern "sftp>"? yes

Upvotes: 4

Views: 2239

Answers (2)

pynexj
pynexj

Reputation: 20797

The following is from the expect's man page:

Expect takes a rather liberal view of scoping. In particular, variables read by commands specific to the Expect program will be sought first from the local scope, and if not found, in the global scope. For example, this obviates the need to place global timeout in every procedure you write that uses expect. On the other hand, variables written are always in the local scope (unless a global command has been issued). The most common problem this causes is when spawn is executed in a procedure. Outside the procedure, spawn_id no longer exists, so the spawned process is no longer accessible simply because of scoping. Add a global spawn_id to such a procedure.

Upvotes: 3

glenn jackman
glenn jackman

Reputation: 247260

I don't have my copy of "Exploring Expect" handy, but I think you're running into a variable scoping issue. spawn invisibly sets a variable named spawn_id. When you call spawn in a proc, that variable is scoped only for that proc. Declare it as global:

proc sftp_connect {} {
  global spawn_id
  # ... rest is the same
}

I think you don't have to do the same thing in sftp_send_password because expect has a more forgiving scoping scheme than Tcl (if expect does not find a local variable, look in the global namespace).

Your sftp_send_password proc will not affect the times variable in sftp_connect though, due to the same variable scoping issue. I'd recommend

proc sftp_send_password {times_var} {
  upvar 1 $times_var times     ;# link this var to that in the caller
  send "${SFTP_PASSWORD}\n";
  expect {
    "sftp>" { puts "Connected"; incr times; exp_continue} 
  }
  # note use of `incr` instead of `expr`
}

And then the sftp_connect proc sends the times variable name:

sftp_send_password times

Upvotes: 5

Related Questions