I have a script that is parsing a local file and execute remotely a new file created with the content of previous one.
Just an example: machine1 with the following command file content:
#cmd1
<blank line here>
#cmd5
hostname -f
reboot`
Now the script will parse that file, will remove blanks and commented lines and create REMOTELY a new file with the new content:
proc _command {fh} {
set fd [open "$fh" r]
#set fp [open "/tmp/script_${fh}" w+]
while { [gets $fd data] >= 0 } {
if { [string length $data] > 0 } {
#skip commented & blank lines
if {[string match "#*" $data] || [string match "" $data]} {
continue
}
#puts $fp "$data"
send "$data\r"
#send [exec echo $data >>/tmp/1.txt]
}
}
#close $fp
}
...
spawn -noecho ssh -i $home/.ssh/.id_rsa -q -o StrictHostKeyChecking=no $user#$server
expect {
-re "($prompt)" {
send "sudo su -\r"
expect {
-re "# " {
_command $cfile
send "exit\r"
Well, for now the part of the procedure that is writing to the file is commented as every time when I execute the script the file is created locally and not on remote machine.
It's something that I'm missing but can't figure out what...
Do you really need expect for this? You're already using private key authentication, so I think all you really need is:
sed -e '/^$/d' -e '/^#/d' local_file | ssh user#host sudo sh -c 'cat > remote_file'
Related
I have a Raspberry Pi image running via a qemu emulator, which I interact with via expect.
I'm trying to capture the output from a particular command within the emulator, and save it to a file on the host.
Being a beginner with Tcl, I read through the manual and had a go at this. The "test.out" file is created but contains only a newline, while "Hello world!" appears on the console.
spawn qemu-system-arm --serial mon:stdio ...
expect {
"login:" { send "pi\r" }
}
expect {
"Password:" { send "raspberry\r" }
}
expect "pi#raspberrypi"
set ftty [exp_open -leaveopen]
set fsignature [open "test.out" w]
send "echo 'Hello world!'\r"
puts $fsignature [gets $ftty]
expect "pi#raspberrypi"
send "sudo shutdown now\r"
wait
I'm not familiar with exp_open. I would normally recommend something like this to capture command output:
set prompt {pi#raspberrypi}
set cmd {echo 'hello world'}
send "$cmd\r"
expect -re "$cmd\r\n(.*)\r\n$prompt"
puts $fsignature $expect_out(1,string)
Extracting command output can be tricky, because the sent command is (typically) displayed and is included in the expect output. This assumes that your specified prompt appears first in its line.
This answer was very useful in finding a solution.
However, for long outputs you need to account for the buffer filling up.
set fd [open "test.out" w]
send "cat large_output\r"
expect {
-re {cat large_output[\r\n]+} { log_user 0; exp_continue }
-ex "\n" { puts -nonewline $fd $expect_out(buffer); exp_continue }
-re $prompt { log_user 1; close $fd }
}
If the line length can exceed the buffer size then something more complicated is needed.
For some reason, the line endings are \r\r\n, but that can be fixed with a sed.
sed -i 's/\r//g' test.out
I am trying to automate xterm window using Expect (though I already knew Expect cant control such GUI applications, but there is a tweaked mechanism explained in Exploring Expect)
package require Expect
spawn -pty
stty raw -echo < $spawn_out(slave,name)
regexp ".*(.)(.)" $spawn_out(slave,name) dummy c1 c2
if {[string compare $c1 "/"] == 0} {
set c1 "0"
}
set xterm_pid [exec xterm -S$c1$c2$spawn_out(slave,fd) &]
close -slave
expect "\n" ;# match and discard X window id
set xterm $spawn_id
spawn $env(SHELL)
Don Libes mentioned that from this point, xterm can be automated and he has given example to use xterm with interact command as follows,
interact -u $xterm "X" {
send -i $xterm "Press return to go away: "
set timeout -1
expect -i $xterm "\r" {
send -i $xterm "Thanks!\r\n"
exec kill $xterm_pid
exit
}
}
But, my expectation is send and expect commands to/from xterm. I have tried the following,
send -i $xterm "ls -l\r"; # Prints commands on xterm
expect -i $xterm "\\\$" ; # Trying to match the prompt
But it didn't workout. This example mainly relies on the xterm's command line option -Sccn.
-Sccn
This option allows xterm to be used as an input and output channel for
an existing program and is sometimes used in specialized applications.
The option value specifies the last few letters of the name of a
pseudo-terminal to use in slave mode, plus the number of the inherited
file descriptor. If the option contains a "/" character, that delimits
the characters used for the pseudo-terminal name from the file
descriptor. Otherwise, exactly two characters are used from the option
for the pseudo-terminal name, the remainder is the file descriptor.
Examples:
-S123/45
-Sab34
Note that xterm does not close any file descriptor which it did not open for its own use. It is possible (though probably not
portable) to have an application which passes an open file descriptor
down to xterm past the initialization or the -S option to a process
running in the xterm.
Where am I making the mistake ?
Here I have you a view from my code I used. It is extracted from a complex part.
# create pty for xterm
set spawn(PTTY,PID) [spawn -noecho -pty]
set spawn(PTTY,DEVICE) $spawn_out(slave,name)
set spawn(PTTY) $spawn_id
stty raw -echo < $spawn(PTTY,DEVICE)
regexp ".*(.)(.)" $spawn_out(slave,name) dummy c1 c2
if {[string compare $c1 "/"] == 0} { set c1 0 }
# Start XTERM (using -into can place the xterm in a TK widget)
set pid(XTERM) [::exec xterm -S$c1$c2$spawn_out(slave,fd) {*}$addidtionlXtermOptions &]
close -slave
# Link
set spawn(SHELL,PID) [spawn -noecho {*}$commandInXterm]
set spawn(SHELL) $spawn_id
set spawn(SHELL,DEVICE) $spawn_out(slave,name)
# ...
# send a key or string into the xterm
exp_send -raw -i $spawn(SHELL) -- $key
exp_send -raw -i $spawn(SHELL) -- "$str\r
As Mr.Thomas Dickey pointed out here, I started exploring on the multixterm
and finally able to make a standalone version where the commands are sent to xterm directly.
The part which mainly I have missed in my code is expect_background which actually does the linking in the background. Hope it helps to all those who all wanted to automate the xterm. All credits to Mr.Thomas Dickey and Mr.Don Libes!!!
#!/usr/bin/tclsh
package require Expect
set ::xtermStarted 0
set xtermCmd $env(SHELL)
set xtermArgs ""
# set up verbose mechanism early
set verbose 0
proc verbose {msg} {
if {$::verbose} {
if {[info level] > 1} {
set proc [lindex [info level -1] 0]
} else {
set proc main
}
puts "$proc: $msg"
}
}
# ::xtermSid is an array of xterm spawn ids indexed by process spawn ids.
# ::xtermPid is an array of xterm pids indexed by process spawn id.
######################################################################
# create an xterm and establish connections
######################################################################
proc xtermStart {cmd name} {
verbose "starting new xterm running $cmd with name $name"
######################################################################
# create pty for xterm
######################################################################
set pid [spawn -noecho -pty]
verbose "spawn -pty: pid = $pid, spawn_id = $spawn_id"
set ::sidXterm $spawn_id
stty raw -echo < $spawn_out(slave,name)
regexp ".*(.)(.)" $spawn_out(slave,name) dummy c1 c2
if {[string compare $c1 "/"] == 0} {
set c1 0
}
######################################################################
# start new xterm
######################################################################
set xtermpid [eval exec xterm -name dinesh -S$c1$c2$spawn_out(slave,fd) $::xtermArgs &]
verbose "xterm: pid = $xtermpid"
close -slave
# xterm first sends back window id, save in environment so it can be
# passed on to the new process
log_user 0
expect {
eof {wait;return}
-re (.*)\n {
# convert hex to decimal
# note quotes must be used here to avoid diagnostic from expr
set ::env(WINDOWID) [expr "0x$expect_out(1,string)"]
}
}
######################################################################
# start new process
######################################################################
set pid [eval spawn -noecho $cmd]
verbose "$cmd: pid = $pid, spawn_id = $spawn_id"
set ::sidCmd $spawn_id
######################################################################
# link everything back to spawn id of new process
######################################################################
set ::xtermSid($::sidCmd) $::sidXterm
set ::xtermPid($::sidCmd) $xtermpid
######################################################################
# connect proc output to xterm output
# connect xterm input to proc input
######################################################################
expect_background {
-i $::sidCmd
-re ".+" {
if {!$::xtermStarted} {set ::xtermStarted 1}
sendTo $::sidXterm
}
eof [list xtermKill $::sidCmd]
-i $::sidXterm
-re ".+" {
if {!$::xtermStarted} {set ::xtermStarted 1}
sendTo $::sidCmd
}
eof [list xtermKill $::sidCmd]
}
vwait ::xtermStarted
}
######################################################################
# connect main window keystrokes to all xterms
######################################################################
proc xtermSend {A} {
exp_send -raw -i $::sidCmd -- $A
}
proc sendTo {to} {
exp_send -raw -i $to -- $::expect_out(buffer)
}
######################################################################
# clean up an individual process death or xterm death
######################################################################
proc xtermKill {s} {
verbose "killing xterm $s"
if {![info exists ::xtermPid($s)]} {
verbose "too late, already dead"
return
}
catch {exec /bin/kill -9 $::xtermPid($s)}
unset ::xtermPid($s)
# remove sid from activeList
verbose "removing $s from active array"
catch {unset ::activeArray($s)}
verbose "removing from background handler $s"
catch {expect_background -i $s}
verbose "removing from background handler $::xtermSid($s)"
catch {expect_background -i $::xtermSid($s)}
verbose "closing proc"
catch {close -i $s}
verbose "closing xterm"
catch {close -i $::xtermSid($s)}
verbose "waiting on proc"
wait -i $s
wait -i $::xtermSid($s)
verbose "done waiting"
unset ::xtermSid($s)
set ::forever NO
}
######################################################################
# create windows
######################################################################
# xtermKillAll is not intended to be user-callable. It just kills
# the processes and that's it. A user-callable version would update
# the data structures, close the channels, etc.
proc xtermKillAll {} {
foreach sid [array names ::xtermPid] {
exec /bin/kill -9 $::xtermPid($sid)
}
}
rename exit _exit
proc exit {{x 0}} {xtermKillAll;_exit $x}
xtermStart $xtermCmd $xtermCmd
xtermSend "ls -l\r"
xtermSend "pwd\r"
vwait ::forever
i have got some problems in my expect script.
I am spawning a scp file upload but the eof is not recognized.
The expect rans into the timeout before the script goes on.
What am i doing wrong??
Here is the script
function ssh_upload_file () {
test_log INFO "Uploading File $3 to Device $2 with a $1 seconds timeout" "ssh_upload_file"
last_log_entry_to_detail
expect <<- DONE
set outfilename "$DETAIL_LOG"
log_file "$DETAIL_LOG";
set timeout $1
set lantime_ip $2
system "touch ~/.ssh/known_hosts"
system "ssh-keygen -R $2"
spawn /bin/bash
sleep 3
send_user "Uploading File via SSH...\n"
send "scp $2 $3:$4 || echo FWTEST_SSH_RESULT=\$?\n"
expect "assword:" {
after 1000
send "$DUT_PWD\r"
#set timeout 5
#expect "y/n"
#send "n\n"
#set timeout $1
#expect "#"
#send "no_shell_timeout\n"
}
expect eof
send_user "Done."
log_file
exit 0
expect eof
DONE
LAST_ERROR=$?
return $LAST_ERROR
}
You spawn a bash shell, and send an scp command. When scp completes, you're sitting at a bash prompt. You need to send bash an exit command before you'll see eof.
Alternatively, you're not doing anything in that bash session except capture the output of the scp command which you can do like this:
spawn scp $2 $3:$4
expect "assword:"
send "$DUT_PWD\r"
expect eof
set info [wait]
puts "FWTEST_SSH_RESULT=[lindex [set info] 3]"
Addressing Donal's comment:
spawn scp $2 $3:$4
expect {
"assword:" {
send "$DUT_PWD\r"
exp_continue
}
eof
}
set info [wait]
puts "FWTEST_SSH_RESULT=[lindex [set info] 3]"
I am trying to create an expect script which will grep a file and return the line which contains the string I am looking for, in this case the string will be a terminal ID. For example I have a file called terminal_list.txt with the following contents:
0x400 192.168.62.133 10006
0x420 192.168.62.133 10021
0x440 192.168.62.133 10022
and I want the line returned which starts with 0x420
My code is as follows:
#!/usr/bin/expect -f
set terminal_list "/home/linkway/terminal_list.txt"
set termid "0x400"
spawn /bin/bash
expect "] "
# Get ip and port of terminal
# Check if termid exists in terminal_list file
set command "grep -q '$termid' '$terminal_list' && echo 'true' || echo 'false'"
send "$command\r"
expect "$command\r\n"
expect -re "(.*)\r\n.*] "
set val $expect_out(1,string)
# If terminal_list does not exist print error and exit
if { [string first "No such file or directory" $val] >= 0 } {
puts "$terminal_list does not exist. Script Failed"
exit
# If terminal_list does not contain termid print error and continue
} elseif { [string match "false" $val] } {
puts "\nTerminal $termid does not exist in ${terminal_list}. Cannot update bootfile.\n"
# If termid is found in terminal_list, get the ip and port of the terminal
} else {
set command "grep '$termid' '$terminal_list'"
send "$command\r"
expect "$command\r\n"
expect -re "(.*)\r\n.*] "
set val $expect_out(1,string)
set ip_port [string range $val 6 end]
}
This works perfectly when I ssh to the RHEL server via putty and run the script in a maximized putty window. HOWEVER when I shrink the window length so that the grep command no longer fits on a single line my code breaks! Can anyone please help me come up with a solution to this? I have been struggling with the processing of expect_out and could really use some guidance.
EDIT: I found what was causing the bug. It turns out that when the grep command is split over multiple lines, a \r is added to the command where the line break is. Here is some debugging info from exp_internal 1. You can see how the \r is added into the grep command where the command ran onto the next line:
expect: does "grep -q '0x400' '/home/linkway/term \rinal_list.txt'
&& echo 'true' || echo 'false'\r\n" (spawn_id exp6)
match glob pattern "grep -q '0x400' '/home/linkway/terminal_list.txt'
&& echo 'true' || echo 'false'\r\n"? no
Why is this happening and what is the proper way to get just the output of the grep command? I find it very strange that expect would behave differently base on how the output of a command is displayed on screen. Any help is greatly appreciated.
I was able to find a cleaner solution to my problem by making my script more expect-like. Here's what it looks like:
set command "grep -q '$termid' '$terminal_list' && echo 'true' || echo 'false'"
send "$command\r"
expect {
-re ".*\r\ntrue\r\n.*] " {
send "grep '$termid' '$terminal_list'\r"
expect ".*\r\n"
expect -re "(.*)\r\n.*] "
set val $expect_out(1,string)
set ip_port [string range $val 6 end]
puts "\nUpdating $termid bootfile"
updatebootfile $ip_port $boot_data $termid
}
-re ".*\r\nfalse\r\n.*] " {
puts "\nTerminal $termid does not exist in ${terminal_list}. Cannot update bootfile.\n"
}
-re "No such file or directory.*] " {
puts "$terminal_list does not exist. Script Failed"
exit 1
}
timeout {
puts "expect timeout when searching for $termid in $terminal_list"
}
}
I have Expect script invoked from a bash script. The Expect script spawns
a ssh session and runs a remote command. I want to have a return code
of the remote command available in my shell script.
# !/bin/sh
expect -f remote.exp 192.168.1.100 user pass 10.10.10.1
# here I want to analyze exit status of remotely invoked command
The expect script is below, here is my ugly workaround:
#!/usr/bin/expect
set timeout 20
set box [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set target [lindex $argv 3]
spawn ssh -l $user $box
expect "*password:"
send "$password\r\n"
expect "*#"
send "ping -c1 $target\r\n"
expect "*#"
send "echo $?\r"
expect {
"0\r" { puts "Test passed."; }
timeout { puts "Test failed."; }
}
expect "*#"
PS. I read that expect_out may help me, but I failed to get it work. Any help
will be greatly appreciated! Thanks.
I have tried the following and it works on a Mac:
#!/usr/bin/expect
# Process command parameters
foreach {host user password target} $argv {break}
set command "ping -c 1 $target"
# Log in
spawn ssh -l $user $host
expect -nocase "password:"
send "$password\r"
# Send the command
expect "$ "
send "$command\r"
# Echo the return code, the key here is to use the format code=X instead
# just a single digit. This way, Expect can find it
expect "$ "
send "echo code=$?\r"
# Analyze the result
set testResult "Test failed."
expect {
"code=0" { set testResult "Test passed." }
}
puts "$testResult"
# Done, exit
expect "$ "
send exit
Note a few differences:
The shell prompt on my system is "$ "
I only send \r, not \r\n. I don't know if it matters
I exit the ssh session by sending "exit"