I have the following expect script.
This is test.exp
#!/usr/bin/expect
# exp_internal 1
# log_file -noappend ~/expect.log
# Use `send_log` to print to log file
set timeout 30
set bold [exec tput bold]
set red [exec tput setaf 1]
set green [exec tput setaf 2]
set normal [exec tput sgr0]
proc test_label {value} {
upvar bold bold
upvar normal normal
puts "Running ${bold}${value}${normal}…"
}
proc test_send {value} {
sleep 0.1
send "$value"
}
proc test_failed {} {
upvar bold bold
upvar red red
upvar normal normal
sleep 0.1
puts "${bold}${red}Failed${normal}"
exit 1
}
proc test_ok {{force_close false}} {
upvar bold bold
upvar green green
upvar normal normal
sleep 0.1
puts "${bold}${green}OK${normal}"
if {$force_close} {
close
}
}
expect_before {
default {
test_failed
}
}
This is electrum.exp
#!/usr/bin/expect
source ./test.exp
test_label "Should create Electrum mnemonic"
spawn qr-backup.sh --create-electrum-mnemonic
expect {
-re {Format USB flash drive \(y or n\)\?} {
test_send "n\r"
}
}
expect {
-re {\[sudo\] password for pi:} {
test_send "$env(password)\r"
}
}
expect {
-re {Creating Electrum mnemonic…}
}
expect {
-re {([a-z]+ ?){24}} {
test_ok true
}
}
Why doesn’t script fail when last line returned by spawn qr-backup.sh --create-electrum-mnemonic is electrum: error: unrecognized arguments: --nbits 264?
Figured it out!
Solved using eof statement.
expect {
-re {([a-z]+ ?){24}} {
test_ok true
}
eof {
test_failed
}
}
Note this from the expect man page:
expect_before [expect_args]
Unless overridden by a -i flag, expect_before patterns match against the spawn id defined at the time that the expect_before command was executed (not when its pattern is matched).
(emphasis mine)
No spawn id was active when the expect_before command was executed.
Related
I am trying to write an expect script that reacts to input from reading a pipe. Consider this example in file "contoller.sh":
#!/usr/bin/env expect
spawn bash --noprofile --norc
set timeout 3
set success 0
send "PS1='Prompt: '\r"
expect {
"Prompt: " { set success 1 }
}
if { $success != 1 } { exit 1 }
proc do { cmd } {
puts "Got command: $cmd"
set success 0
set timeout 3
send "$cmd\r"
expect {
"Prompt: " { set success 1 }
}
if { $success != 1 } { puts "oops" }
}
set cpipe [open "$::env(CMDPIPE)" r]
fconfigure $cpipe -blocking 0
proc read_command {} {
global cpipe
if {[gets $cpipe cmd] < 0} {
close $cpipe
set cpipe [open "$::env(CMDPIPE)" r]
fconfigure $cpipe -blocking 0
fileevent $cpipe readable read_command
} else {
if { $cmd == "exit" } {
exp_close
exp_wait
exit 0
} elseif { $cmd == "ls" } {
do ls
} elseif { $cmd == "pwd" } {
do pwd
}
}
}
fileevent $cpipe readable read_command
vwait forever;
Suppose you do:
export CMDPIPE=~/.cmdpipe
mkfifo $CMDPIPE
./controller.sh
Now, from another terminal try:
export CMDPIPE=~/.cmdpipe
echo ls >> ${CMDPIPE}
echo pwd >> ${CMDPIPE}
In the first terminal the "Got command: ls/pwd" lines are printed immediately as soon as you press enter on each echo command, but there is no output from the spawned bash shell (no file listing and current directory). Now, try it once more:
echo ls >> ${CMDPIPE}
Suddenly output from the first two commands appears but 3rd command (second ls) is not visible. Keep going and you will notice that there is a "lag" in displayed output which seems to be "buffered" and then dumped at once later.
Why is this happening and how can I fix it?
According to fifo(7):
Normally, opening the FIFO blocks until the other end is opened also.
So, in the proc read_command, it's blocking on set cpipe [open "$::env(CMDPIPE)" r] and does not get the chance to display the spawned process's output until you echo ... >> ${CMDPIPE} again.
To work it around, you can open the FIFO (named pipe) in non-blocking mode:
set cpipe [open "$::env(CMDPIPE)" {RDONLY NONBLOCK} ]
This is also mentioned in fifo(7):
A process can open a FIFO in nonblocking mode. In this case, opening for read-only will succeed even if no one has opened on the write side yet ...
The following is the simplified version of your code and it works fine for me (tested on Debian 9.6).
spawn bash --norc
set timeout -1
expect -re {bash-[.0-9]+[#$] $}
send "PS1='P''rompt: '\r"
# ^^^^
expect "Prompt: "
proc do { cmd } {
send "$cmd\r"
if { $cmd == "exit" } {
expect eof
exit
} else {
expect "Prompt: "
}
}
proc read_command {} {
global cpipe
if {[gets $cpipe cmd] < 0} {
close $cpipe
set cpipe [open cpipe {RDONLY NONBLOCK} ]
fileevent $cpipe readable read_command
} else {
do $cmd
}
}
set cpipe [open cpipe {RDONLY NONBLOCK} ]
fileevent $cpipe readable read_command
vwait forever
I am back to Tcl/Expect after a long time. It seems Expect isn't behaving the same for default spawn_id and explicit. AFAIK this wasn't the case.
I am using Expect 5.45 on Ubuntu. On an input file input/input.found.txt like:
~$ more input/input.found.txt
hello
World!
hello
<intentional blank new line>
Consider Expect code (use send_user or puts) where both cases differ only by -i $file1 only:
package require Expect
puts "---"
set file [open "input/input.found.txt" r]
spawn -open $file
set file1 $spawn_id
expect -i $file1 {
-re ".ello" {
send_user "found\r\n"; # should print BUT doesn't print
}
-re ".orld" {
send_user "found the world!\r\n"; # shouldn't print, doesn't print
}
eof { puts "not found" }
}
exp_close $file1
if {[info exists $file ] } {
close $file
}
puts "---"
set file [open "input/input.found.txt" r]
spawn -open $file
set file1 $spawn_id
expect {
-re ".ello" {
send_user "found\r\n"; # should print, prints
}
-re ".orld" {
send_user "found the world!\r\n"; # shouldn't print, doesn't print
}
eof { puts "not found" }
}
exp_close $file1
if {[info exists $file ] } {
close $file
}
puts "---"
I expect outputs for both the cases to be the same but they aren't. The output I get is:
---
spawn [open ...]
hello
World!
hello
---
spawn [open ...]
hello
World!
hello
found
---
(Notice missing found in the first case.)
Where is my assumption about output wrong? Why explicit specification of spawn_id through -i (at least in file handling case) altering behavior of send_user or puts?
Your first expect command is not what you think. The expect command will take seperate arguments, or a single list of arguments.
Your first expect command has 3 arguments, so it takes the -i $file as an option specifying the spawn id and the last argument as a pattern without any body. That multi-line pattern doesn't match your file content. But even if it did, nothing would be printed as there is no body for that pattern.
Your second expect command has all arguments inside braces. The expect command then expands that list into 8 arguments.
The easiest fix for your program is to put the -i $file1 inside the braces, as in:
expect {
-i $file1
-re ".ello" {
send_user "found\r\n"; # should print BUT doesn't print
}
-re ".orld" {
send_user "found the world!\r\n"; # shouldn't print, doesn't print
}
eof { puts "not found" }
}
I have this code that starts a process, expects some startup output, and then logs the rest to a file:
proc foo { } {
set log_fp [open "somefile" a]
exec cp $prog "$prog.elf"
spawn someprog
set someprog_spawn_id $spawn_id
# do some things here that that wait for output from someprog
expect {
-i $someprog_spawn_id
-re "Some output indicating successful startup"
}
# send the process into the background
expect_background {
-i $someprog_spawn_id
full_buffer { }
eof {
wait -i $someprog_spawn_id
close $log_fp
}
-re {^.*\n} {
puts $log_fp $expect_out(buffer)
}
}
}
Unfortunately, this errors with the message:
can't read "log_fp": no such variable
How can I access this variable within this scope?
The expect_background callback scripts are evaluated in the global scope (because the procedure may well have finished at the point when they fire) so you have to put the variable in that scope as well…
proc foo { } {
global log_fp
set log_fp [open "somefile" a]
# ...
Alternatively, with 8.5 you can do some tricks with using apply to make a binding
expect_background "
-i \$someprog_spawn_id
full_buffer { }
[list eof [list apply {log_fp {
wait -i $someprog_spawn_id
close $log_fp
}} $log_fp]]
[list -re {^.*\n} [list apply {log_fp {
puts $log_fp $expect_out(buffer)
}} $log_fp]]
"
Really ugly though. Using a global variable is a lot easier.
I have a expect script :
expect - "$#" << 'END_OF_FILE'
set username [lindex $argv 0]
set hosts [lindex $argv 1]
set Passwordfile [lindex $argv 2 ]
set Commands "[lindex $argv 3 ];\r"
set prompt [lindex $argv 4 ]
# log_user 0
#exp_internal 1
if { [llength $argv] != 5} {
puts "usage: username hostname password commands prompt"
exit 1
}
set force_conservative 0 ;# set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
#
#COMMENTS
#SendCommands function sends the all the commands passed to it.All the commands passed to it must be separated by a semicolon and put a \r at last
set pfile [ open "$Passwordfile" "r"]
proc SendCommands { Commands } {
global prompt log errlog
foreach element [split $Commands ";"] {
expect {
-re $prompt
{send -- "$element\r"}
}
}
}
set timeout 20
foreach host [ split $hosts "\;" ] {
spawn ssh -o "StrictHostKeyChecking no" "$username#$host"
match_max 1000000
set expect_out(buffer) {}
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
"*?assword:*"
{
send -- "[read $pfile]\r"
seek $pfile 0 start
}
}
expect {
timeout { send_user "\nLogin incorrect\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
-re "$prompt"
{ send -- "\r" }
}
set timeout 60
close "$pfile"
SendCommands "$Commands"
}
END_OF_FILE
i can execute it like :
./scriptname username hostname passwordfile "commnmad1;commnad2;command3" "(.*#|.*>)$"
but if i change modes by executing enable command i will get password prompt instead of
the usual prompt (# or >). how can i make sure that the below code is executed if the command is enable or i get a password prompt.
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
"*?assword:*"
{
send -- "[read $pfile]\r"
seek $pfile 0 start
}
}
You probably want
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
"*?assword:*" {
send -- "[read -nonewline $pfile]\r"
seek $pfile 0 start
exp_continue
}
$prompt
}
I added the -nonewline option to the read command. When you send the password, exp_continue will keep you in this expect "loop" until one of the other conditions is met, including the prompt.
I dont know whats happening but i am not getting the complete output from the remote command executed possibly because expects internal buffer is getting execceded.
proc SendCommands { Commands } {
global prompt log errlog
foreach element [split $Commands ";"] {
expect {
-re $prompt
{send -- "$element\r"}
}
set outcome "$expect_out(buffer)"
foreach line [split $outcome \n] {
foreach word [regexp -all -inline {\S+} $line] {
if {( [string index [string trimleft $line " "] 0 ] == "%")} {
puts "$outcome"
exit 1
}
}
}
puts "$outcome"
}
}
set timeout 30
foreach host [ split $hosts "\;" ] {
spawn ssh -o "StrictHostKeyChecking no" "$username#$host"
match_max 10000000
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
"*?assword:*"
{
send -- "$password\r"
}
}
expect {
timeout { send_user "\nLogin incorrect\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
-re "$prompt"
{ send -- "\r" }
}
set timeout 300
SendCommands "$Commands"
}
this is how i am executing it :
./sendcommand aehj SWEs-elmPCI-B-01.tellus comnet1 "terminal length 0;show int description" "(.*#)$"
i am getting the complete output only when i remove log user 0 but when i use the puts command in the fucnction sendcommands above i get about 90 percent of it with 10 percent
of the trailing data at the end is not shown.
one way i am thinking is to use negation of regex in expect but it doesn't seem to work.
expect {
-re ! $prompt
{puts $expect_outcome(buffer)}
}
EDIT :I get all the output once when its executed about 5 or 7 times
After a little search i came up with this and seems to work but let me know of any execptions or better answers :
I set match_max = 1000 then
expect {
-re $prompt
{send -- "$element\r"}
full_buffer {
append outcome $expect_out(buffer)
exp_continue
}
}
append outcome $expect_out(buffer)
puts $outcome
but still when i set match_max = 10000 or 100 it fails again