Issue
I've been trying to write an expect script which has one method to open an ssh connection and this method can be called by other methods so that they can send additional commands for execution. What I already have is -
#!/usr/bin/expect
set timeout -1
#log_user 0
set username [lindex ${argv} 0]
set node_address [lindex ${argv} 1]
set password [lindex ${argv} 2]
set prompt ".*\[>#:$%\] \?\$"
set password_prompt ".*: \?\$"
set install_dir "/usr/share/elasticsearch"
set conf_dir "/etc/elasticsearch"
set plugin_manager "${install_dir}/bin/elasticsearch-plugin"
set es_config_file "${conf_dir}/elasticsearch.yml"
puts "Attempting login to ${node_address}"
spawn ssh ${username}@${node_address}
expect -re ${password_prompt} {send -- "${password}\n"}
expect -re ${prompt} {send -- "sudo su - kompuser\n"}
expect -re ${prompt}
puts "Logged in to the node ${node_address} successfully"}
expect -re ${prompt}
send -- "curl -XGET 'localhost:9200/_cat/nodes?v&h=ip,node.role'\n"
expect -re ${prompt}
send_user $expect_out(buffer)
I have other scripts which try to login to a host using the similar code and have lots of code duplication. I want to avoid the code duplication by writing a single method, let's call it login
and invoke it before sending any command to the remote server, what I want is -
#!/usr/bin/expect
set timeout -1
#log_user 0
set username [lindex ${argv} 0]
set node_address [lindex ${argv} 1]
set password [lindex ${argv} 2]
set prompt ".*\[>#:$%\] \?\$"
set password_prompt ".*: \?\$"
set install_dir "/usr/share/elasticsearch"
set conf_dir "/etc/elasticsearch"
set plugin_manager "${install_dir}/bin/elasticsearch-plugin"
set es_config_file "${conf_dir}/elasticsearch.yml"
proc login {username node_address password prompt password_prompt} {
puts "Attempting login to ${node_address}"
spawn ssh ${username}@${node_address}
expect -re ${password_prompt} {send -- "${password}\n"}
expect -re ${prompt} {send -- "sudo su - kompuser\n"}
puts "Logged in to the node ${node_address} successfully"}
proc get_node_info {username node_address password prompt password_prompt} {
login ${username} ${node_address} ${password} ${prompt} ${password_prompt}
expect -re ${prompt}
send -- "curl -XGET 'localhost:9200/_cat/nodes?v&h=ip,node.role'\n"
expect -re ${prompt}}
proc restart_node {username node_address password prompt password_prompt} {
login ${username} ${node_address} ${password} ${prompt} ${password_prompt}
expect -re ${prompt} {send -- "sudo systemctl daemon-reload\n"}
expect -re ${prompt} {send -- "sudo systemctl start elasticsearch.service\n"}
expect -re ${prompt}
}
get_node_info ${username} ${node_address} ${password} ${prompt} ${password_prompt}
restart_node ${username} ${node_address} ${password} ${prompt} ${password_prompt}
The issue is that call to login logs in successfully but expecting for the prompt in the caller hangs - Following are the last few lines of the logs on enabling the debug logs -
send: sending "sudo su - kompuser\n" to { exp4 }
Logged in to the node 10.109.14.73 successfully
Gate keeper glob pattern for '.*[>#:$%] ?$' is ''. Not usable, disabling the performance booster.
expect: does "" (spawn_id exp0) match regular expression ".*[>#:$%] ?$"? (No Gate, RE only) gate=yes re=no
Any help will be much appreciated. Thanks
Solution
When you run spawn
inside a proc, the variable spawn_id
is created in the current stack frame, making it a local variable unless special measures are taken.
With that knowledge, the solution is simple: Declare spawn_id
as a global before running the spawn
command:
proc login {username node_address password prompt password_prompt} {
global spawn_id
puts "Attempting login to ${node_address}"
spawn ssh ${username}@${node_address}
expect -re ${password_prompt} {send -- "${password}\n"}
expect -re ${prompt} {send -- "sudo su - kompuser\n"}
puts "Logged in to the node ${node_address} successfully"
}
I believe you don't need to do that in the other procs. But for consistency, I would.
Answered By - Schelte Bron