Expect - Expecting while interacting - expect

I want to "listen" for a string outputted by the shell, while being in "interact" mode. Or i want to emulate interact mode somehow, that still allows me to listen for a specific string from the shell.
It seems interact only listens to the users input (the keys I press) and not what is returned by the shell.
How would I go about having Expect executing something everytime it sees a specific string, but otherwise let me use the shell interactively an unhindered?.
Example:
proc yay {} {
send_user "Yay\n"
}
trap { # trap sigwinch and pass it to the child we spawned
set rows [stty rows]
set cols [stty columns]
stty rows $rows columns $cols < $spawn_out(slave,name)
} WINCH
spawn bash
interact {
interact -re "HOT" {
yay
}
expect {
fuzz yay
}
}
If i run this and type "HOT" it responds with "Yay". As expected, it read my keys. But if i type
echo fuzz
The "expect" clause doesnt get triggered. Also "echo HOT" wont trigger anything either.
So is this possible or am I missing somthing. Perhaps I'd need to emulate interact in some kind of "expect, continue"-loop. Its just important that everything works normally in the shell..
Suggestions anyone?

You can use the expect_background command. From the man page:
takes the same arguments as expect, however it returns immediately. Patterns are tested whenever new input arrives.
You can modify your initial script like this:
#!/usr/bin/expect
proc yay {} {
send_user "Yay\n"
}
trap { # trap sigwinch and pass it to the child we spawned
set rows [stty rows]
set cols [stty columns]
stty rows $rows columns $cols < $spawn_out(slave,name)
} WINCH
spawn bash
expect_background {
fuzz yay
}
interact -re "HOT" {
yay
}

Related

Sending commands to "application's shell" using "bash script" [duplicate]

This question already has answers here:
Is it possible to make a bash shell script interact with another command line program?
(6 answers)
Closed 1 year ago.
I have a program JLinkExe which opens it's own prompt uponn execution. So I normally run it like this:
JLinkExe
and then type the commands at it's prompt that appears:
J-Link>
There are many applications with it's own prompt and I am interested in a general method that would enable me to send commands to any kind of application that has it's own prompt.
I already tried two methods. They both try to send commands in this order:
connect
Enter
Enter
Enter
Enter
erase
loadbin program.bin , 0x0
r
q
but both fail. Here is the first method:
{ echo 'connect';
echo '';
echo '';
echo '';
echo '';
echo 'erase';
echo 'loadbin program.bin , 0x0';
echo 'r';
echo 'q'; } | JLinkExe
And the second method (source):
JLinkExe <<EOF
connect
erase
loadbin program.bin , 0x0
r
q
EOF
I found these method on the internet but I don't understand why they fail. Especially the first one that worked in the past...
Can anyone propose any better / working / universally applicable method?
I think it might be because here-docs do not wait for output. Unfortunately for you I switched company, thus can't test my code below.
#! /bin/bash
expect <<-EOF
set timeout -1
spawn JLinkExe
expect "J-Link> " { send "connect\r" }
expect "J-Link> " { send "\r" }
expect "J-Link> " { send "\r" }
expect "J-Link> " { send "\r" }
expect "J-Link> " { send "\r" }
expect "J-Link> " { send "erase\r" }
expect "J-Link> " { send "loadbin program.bin , 0x0\r" }
expect "J-Link> " { send "r\r" }
expect "J-Link> " { send "q\r" }
expect eof
catch wait result
exit [lindex \$result 3]
EOF
exit $?
Except waits until J-Link> turns up and then sends the command through the connection.
If it doesn't work please notify me. I'll try to help you after the weekend :-)
EDIT:
A: Why did you wrap everything in expect 2>&1 <<-EOF and EOF?
You can add expect in the shebang, but I often use it as part of my Bash scripts. My knowledge of Bash is better.
B: Why a -EOF instead of EOF?
That's because <<-EOF allows leading tabs when you want to end the here-doc. You can indent it in functions for instance.
C: Why did you redirect stderr to stdout (2>&1)?
In your case I should've removed this. I took the code from one of my other answer about expect and tailored it to your needs.
D: What does catch wait result and exit [lindex \$result 3] do after we catch the eof?
Nice question, I had to look this one up a little myself:
lindex takes 4rd argument in \$result and exits the here-doc (0 is arg 1).
\$result is set by catch wait result.
Catch takes the output of wait and puts that into result.
Wait returns four integers:
First: pid of process that's being waited on.
Second: spawn ID.
Third: -1 for errors, 0 otherwise.
Forth: Exit status of the program as set by the OS.
Sources:
https://linux.die.net/man/1/expect
https://www.tcl.tk/man/tcl/TclCmd/catch.html
https://www.tcl.tk/man/tcl/TclCmd/lindex.html
Note that you have to escape the $ in the here-doc, otherwise Bash tries to process it. Hence \$result.
E: Why you exit with exit $?
Bash exits a script with the last known error code. Although you can leave it implicitly, I like to add it anyhow. It keeps the script more readable for beginners.

How to execute the same expect simultaneously for multiple hosts after spawn

I am trying to ssh to multiple hosts at the same time and execute the same commands simultaneously for all. I am using expect to log in and send the commands automatically. The script that i created bellow works but connects and executes the commands serially for each host one after the other. What I want is to put the expect to work simultaneously for all the hosts like creating a child process for each host or working in the background.
Any ideas how to reach that?
For my code I am reading a file that includes multiple IP addresses and pass it to the script.
Here is my code:
#! /bin/expect
set prompt ">"
set fd [open ./hosts r]
set hosts [read -nonewline $fd]
close $fd
foreach host [split $hosts "\n" ] {
set timeout 30
spawn ssh admin#$host
lappend spawn_id_list $spawn_id
}
foreach id $spawn_id_list {
set spawn_id $id
while (1) {
expect {
"ssh:" {
exit
}
"no)? " {
send "yes\r"
}
"password: " {
send "password\r"
}
"$prompt" {
send "some commands\r"
break
}
timeout {
exit
}
-re . {
exp_continue
}
eof {
exit
}
}
}
}
expect eof
What about using Expect's fork?
According to Expect's manual:
fork creates a new process. The new process is an exact copy of the
current Expect process. On success, fork returns 0 to the new
(child) process and returns the process ID of the child process
to the parent process. On failure (invariably due to lack of
resources, e.g., swap space, memory), fork returns -1 to the
parent process, and no child process is created.
Forked processes exit via the exit command, just like the original process. Forked processes are allowed to write to the log
files. If you do not disable debugging or logging in most of
the processes, the result can be confusing.
Some pty implementations may be confused by multiple readers and
writers, even momentarily. Thus, it is safest to fork before
spawning processes.

Expect Script - Fixing weird terminal resizing behaviour

I wrote a small expect script to connect to multiple SSH Servers.
However if I interact with the terminal after initializing the connection, the terminal window behaves very odd.
For example, if I start the script in a non-fullsize terminal, and resize the window to make it larger after the Interact, it looks like that:
Now, I had this issue in my first expect script as well. But I was able to resolve it by adding the following code:
trap {
set XZ [stty rows ]
set YZ [stty columns]
stty rows $XZ columns $YZ < $spawn_out(slave,name)
} WINCH
And this worked perfectly fine! I was able to resize the terminal without a problem.
However, I added this piece of code to my new script, where there are multiple interact's in different proc's (functions). And on window resize, I get the following error:
can't read "spawn_out(slave,name)": no such variable
while executing
"stty rows $XZ columns $YZ < $spawn_out(slave,name)"
I have no idea on how to resolve this. Here is my code:
#!/usr/bin/expect -f
set SERVER "0"
set CHOICE "0"
set SERVER_1_PKEY [exec cat /home/me/p1]
set SERVER_2_PKEY [exec cat /home/me/p2]
set SERVER_1_HOST "server1.com"
set SERVER_2_HOST "server2.com"
set SERVER_1_USER "server1user"
set SERVER_2_USER "server2user"
set SERVER_1_PORT "22"
set SERVER_2_PORT "22"
trap {
set XZ [stty rows ]
set YZ [stty columns]
stty rows $XZ columns $YZ < $spawn_out(slave,name)
} WINCH
proc s1 {SERVER_1_PKEY SERVER_1_HOST SERVER_1_USER SERVER_1_PORT} {
send_user "\033c"
spawn ssh ${SERVER_1_USER}#${SERVER_1_HOST} -p ${SERVER_1_PORT}
expect "assword:"
send "${SERVER_1_PKEY}\r"
interact
}
proc s2 {} {
send_user "\033c"
spawn ssh ${SERVER_2_USER}#${SERVER_2_HOST} -p ${SERVER_2_PORT}
expect "assword:"
send "${SERVER_2_PKEY}\r"
interact
}
set arg [lindex $argv 0]
switch $arg {
"" { set CHOICE "0" }
"1" { set CHOICE "1" }
"2" { set CHOICE "2" }
}
if {$CHOICE eq "0"} {
puts -nonewline " Input \[1,2\]: "
flush stdout
gets stdin SERVER
if {$SERVER eq "1"} { s1 $SERVER_1_PKEY $SERVER_1_HOST $SERVER_1_USER $SERVER_1_PORT }
if {$SERVER eq "2"} { s2 $SERVER_2_PKEY $SERVER_2_HOST $SERVER_2_USER $SERVER_2_PORT }
}
if {$CHOICE eq "1"} { s1 $SERVER_1_PKEY $SERVER_1_HOST $SERVER_1_USER $SERVER_1_PORT }
if {$CHOICE eq "2"} { s2 $SERVER_2_PKEY $SERVER_2_HOST $SERVER_2_USER $SERVER_2_PORT }
Can anyone help me resolve this issue or tell me what I'm missing?
When you call spawn inside a procedure the array variable spawn_out(slave,name) has the scope of that procedure only. Usually, you can just make this into a global scope by declaring it as such inside each procedure:
proc s1 {...} {
global spawn_out
...
spawn ...
}
send_user $spawn_out(slave,name)

Set timeout for expect script if execution time is input based

I have an expect script, which executes remote shell script.Now the time taken to execute shell script is based on inputs. More input, more time, less input less time.How should I set my timeout value because if set timeout as -1, then it will exit only with eof, and in case script gets hanged, then we will have a hanged session.
After analyzing the output pattern of the ongoing script, I found "OK" coming for all the lines read from the input file.So, I used it as a progress indicator to reset my timeout counter.
Following is the fraction of code I wrote to resolve my problem :
send -- "bash scriptname.sh \r"
expect {
-re "OK" {
exp_continue
}
-re "Enter XYZ value:" {
send "0000\r"
}
timeout {
exit
}
eof {
exit
}
}

how to make command "ps" don't show password in expect script?

I have make an example as below. The password(mingps)is the shell variable. When execute the shell script, in the mean while, execute command "ps -ef", I found the result of "ps" showed the password(mingps). For security reason, I don't want to show the password when execute command "ps -ef". So how to hide it? Thanks in advance.
#!/bin/sh
MalbanIP="XXX.XXX.XXX.XXX"
MalbanLogin="ming"
MalbanPwd="mingps"
MalbanCmd="netstat"
firstTime="true"
/usr/bin/expect <<EOF
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"; set firstTime "false"; exp_continue
}
}
"password" {
if {$firstTime == "true"} {
send "$MalbanPwd\r"; set firstTime "false"
} else {
log_user 1; puts stdout "password is wrong"; log_user 0;
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
EOF
exit 0
Option 1
The best way is to switch to using RSA keys to log in, as this will enable you to significantly strengthen your overall system security substantially. With that, you can probably avoid using Expect entirely.
Option 2
However, if you can't do that, the key to fixing things is to not pass it as either an argument or an environment variable (since ps can see both with the right options). Instead, you pass the password by writing it into a file and giving the name of that file to the Expect script. The file needs to be in a directory that only the current user can read; chmod go-rx will help there.
MalbanPwdFile=/home/malban/.securedDirectory/examplefile.txt
# Put this just before the spawn
set f [open $MalbanPwdFile]
set MalbanPwd [gets $f]
close $f
You might also need to put a backslash in front of the use of $MalbanPwd so that it doesn't get substituted by the shell script part too early.
Option 3
Or you could stop using that shell wrapper and do everything directly in Tcl/Expect.
#!/usr/bin/expect
set MalbanIP "XXX.XXX.XXX.XXX"
set MalbanLogin "ming"
set MalbanPwd "mingps"
set MalbanCmd "netstat"
set firstTime true
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"
set firstTime false
exp_continue
}
}
"password" {
if {$firstTime} {
send "$MalbanPwd\r"
set firstTime false
} else {
log_user 1
puts stdout "password is wrong"
log_user 0
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
I suspect that this last option will work best for you in the longer term. It's definitely the simplest one (other than switching to RSA keys, which is what I've got deployed on my own infrastructure) and I think it is going to avoid some subtle bugs that you've got in your current code (due to substitution of variables at the wrong time).

Resources