David00
David00

Reputation: 132

How can I share a spawned SSH process between multiple procedures in TCL Expect?

Edit: I've figured it out by referencing the Exploring Expect book. Please don't hesitate to add to my answer or make other suggestions, though! I will mark this as answered when I am allowed to (2 days from now).

I've looked around and unfortunately I'm not able to find too much on using SSH with expect here on this site. I am relatively new to Expect but I have been teaching myself with the Exploring Expect book.

My question is: How can I use a single spawned SSH process for multiple tcl procedures? My workaround right now is to close the SSH connection at the end of procedure 1 and re-spawn a new SSH connection in procedure 2.

Example: (The example is simplified quite a bit and only includes the necessary components to demonstrate my question... My whole program is over 200 lines as of now)

;# Proc definition for procedure1
    proc procedure1 {user host pw} {
     spawn /usr/bin/ssh $user@$host
     expect "Password:"
     send "$pw\r"
     expect "#" ;# This is my device's prompt
     ;# From here it does a bunch of stuff... sends commands to the SSH
     ;# session, captures output, builds arrays and lists, etc
     send "exit" ;# Disconnects the SSH session
     return $mylist ;# returns a list of numbers to be used in procedure 2
     }

;# Proc definition for procedure2
    proc procedure2 {resultofproc1 user host pw} {    
     spawn /usr/bin/ssh $user@$host
     expect "Password:"
     send "$pw\r"
     expect "#" ;# This is my device's prompt
     ;# Proc 2 now continues on in the same device using the results (a 
     ;# list) from proc1. 
     return
    }



    ;# Procedure call for first procedure:
    set resultofproc1 "[procedure1 $user $host $pw]"

    ;# Procedure call for second procedure:
    procedure2 $resultofproc1 $user $host $pw

Instead of closing the SSH connection at the end of procedure1 and reopening the SSH connection at the beginning of procedure2, how can I send commands from procedure2 to the SSH connection opened in procedure1? Obviously I would have to remove the sending of exit to keep the connection open. Assuming this is possible... Is this even a good practice or am I better off separating connections between procedures? If so, can you modify my code example to demonstrate how?

From what I have gathered, I think it has to do with the spawn_id variable.. but I can't figure out how to implement that in my code. I am currently reviewing chapter 10 "Handling Multiple Processes" in the Exploring Expect book. I will report back if I can figure it out on my own.

Thanks for your assistance!

I have viewed these answers on stackoverflow:

Tcl Expect Keep SSH Spawn open

Expect Procedure for SSH Login

Using procedure for spawing SSH doesn't work properly with expect

Upvotes: 2

Views: 960

Answers (2)

pynexj
pynexj

Reputation: 20688

You can use global to declare spawn_id as a global var. According to expect's manual:

CAVEATS

...
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.
...


You can also take advantage Tcl's upvar command. For example:

[STEP 101] $ cat foo.exp
proc expect_prompt {} {
    upvar spawn_id spawn_id
    expect -re {bash-[.0-9]+[#$] $}
}

proc open_conn {} {
    upvar spawn_id spawn_id
    spawn bash --noprofile --norc
    expect_prompt
}

proc close_conn {} {
    upvar spawn_id spawn_id
    send "exit\r"
    expect eof
}

proc send_cmd { cmd } {
    upvar spawn_id spawn_id
    send "$cmd\r"
    expect_prompt
}

proc main {} {
    open_conn

    send_cmd "echo spawn_id=$spawn_id"
    send_cmd "ps Tu"

    close_conn
}

main
[STEP 102] $ 

Output:

[STEP 103] $ expect foo.exp
spawn bash --noprofile --norc
bash-4.4$ echo spawn_id=exp6
spawn_id=exp6
bash-4.4$ ps Tu
USER     PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root   71513   0.0  0.0  2433012    788 s013  R+   12:58PM   0:00.00 ps Tu
pynexj 71510   0.0  0.0  2445360   1572 s013  Ss   12:58PM   0:00.01 bash --noprofile --norc
bash-4.4$ exit
exit
[STEP 104] $

Upvotes: 2

David00
David00

Reputation: 132

Ah, I was so close! Had I taken a few more minutes in the Exploring Expect book, I would have figured it out. Anyways, here is my solution. Hopefully it helps someone else with the same problem.

It has everything to do with the spawn_id variable. Basically, by default, the send and expect commands interact only with the last spawned process. Each spawned process is assigned a unique spawn_id value. You can set the spawn_id of a procedure to the spawn_id of another spawned process.

Here is the code in my original question modified to support the spawn_id variable:

;# Proc definition for procedure1
    proc procedure1 {user host pw} {
     spawn /usr/bin/ssh $user@$host
     global procedure1_spawnid         ;# Creates a global variable called
                                       ;# procedure1_spawnid
     set procedure1_spawnid $spawn_id  ;# Assigns the spawn_id of the SSH process to the 
                                       ;# global variable.
     expect "Password:"
     send "$pw\r"
     expect "#" ;# This is my device's prompt
     ;# From here it does a bunch of stuff... sends commands to the SSH
     ;# session, captures output, builds arrays and lists, etc
     return $mylist ;# returns a list of numbers to be used in procedure 2
     }

;# Proc definition for procedure2
    proc procedure2 {resultofproc1} {    
     global procedure1_spawnid
     set spawn_id $procedure1_spawnid   ;# Sets the spawn_id to that of the SSH connection
                                        ;# in procedure1.
     send "show ip interface brief"
     expect "#"
     ;# The show ip interface brief command is sent to the device through the 
     ;# SSH connection opened in procedure1.
     return
    }



    ;# Procedure call for first procedure:
    set resultofproc1 "[procedure1 $user $host $pw]"

    ;# Procedure call for second procedure:
    procedure2 $resultofproc1

More information can be found in Exploring Expect, page 233.

If anyone has any suggestions or improvements, please feel free to lay them on me! I am still learning tcl/expect.

Upvotes: 0

Related Questions