Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 759
meso_2600 Avatar asked Sep 21 '25 10:09

meso_2600


1 Answers

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
like image 166
glenn jackman Avatar answered Sep 23 '25 15:09

glenn jackman