Set timeout for expect script if execution time is input based - shell

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

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 make expect's expect command fail if the output isn't matched?

Given the very simple script script.expect
#!/usr/bin/expect
spawn bash
expect "#"
send "/bin/false; echo \"process returned with $?\"\r"
expect -exact "process returned with 0"
send -- "exit\r"
expect eof
I don't seem to be able how the script can not fail since /bin/false will cause the echo command to print process returned with 1, thus process returned with 0 can never be matched on the expect command. I expect expect script.expect to fail with return code 1 after expect -exact "process returned with 0".
#!/usr/bin/expect
spawn bash
expect "#"
send "/bin/true; echo \"process returned with $?\"\r"
expect -exact "process returned with 0" {
send -- "exit\r"
expect eof
exit 0
}
exit 1
Even if I change the logic of my "application" in order to be able to test it with a positive/logically negated flow the outcome is still unexplainable.
I worked through
How to make expect command in expect program script to wait for exact string matching
https://www.thegeekstuff.com/2010/10/expect-examples
https://unix.stackexchange.com/questions/66520/error-handling-in-expect
https://unix.stackexchange.com/questions/79310/expect-script-within-bash-exit-codes?rq=1
and have no clue why expect is behaving this way.
In your first script, the expect -exact... command is "succeeding" with a timeout. The default timeout is 10 seconds, and the default action on timeout is to do nothing. So the commands waits 10 seconds, matches timeout, and returns, so we continue with the next command.
You can explicitly match for timeout:
expect {
-exact "process returned with 0" {}
timeout { puts "timeout!"; exit 1 }
}
To avoid the wait to timeout, you can use a regexp that will match whether $? is 0 or 1 (or other numbers). If you put part of the regexp in a capture group (), you can then find it in built-in variable $expect_out(1,string):
expect -re {process returned with ([0-9]+)}
set returncode $expect_out(1,string)
puts "we got $returncode"
exit $returncode
Note, the regexp uses {} style quotes, because "" quotes dont allow you to use [] inside them.

Global timeout handler in expect?

I've got an expect script that looks a bit like this:
set timeout 15
spawn someprocess
expect "a line"
expect "another line"
expect "some other line"
Essentially, it's waiting until these lines appear. There are no actions to be taken.
I don't want to write the following for every line that I'm looking for:
expect {
"a line" {}
timeout { exit 1 }
}
I want expect to return a non-zero status code (i.e. in $?) if it times out at any point. How do I do this?
You can setup an expect_before line that is run "in parallel" with any later expect commands to test for timeout. Just add after your spawn command
expect_before timeout { exit 1 }
If you want to error out if the spawned process exits, you can combine them as follows:
expect_before {
timeout { exit 1 }
eof { exit 1 }
}

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

Remove send command from terminal output while keeping needed output

I am trying to create a script that will log onto a server, run some commands while providing information back to the user.
I can log onto the server fine using the script, my issue is the output I get back. My script is something like this:
#!/bin/bash
/usr/bin/expect << SSHLOGIN
set timeout 600
spawn ssh user#myServer.com
expect {
"Are you sure you want to continue connecting (yes/no)?" {
send "yes\n";exp_continue
}
Password: {
send "$2\n"
}
}
expect {
"# " {
send "echo \"Current Directory is:\" \n"
}
}
expect "# " {
send "pwd \n"
}
expect {
"# " {
send "exit \n"
}
}
wait
SSHLOGIN
& my output is as follows:
spawn ssh user#myServer.com
Password:
You have new mail.
DISPLAY set to user.myServer:0.0
# echo "Current Directory is:"
Current Directory is:
# pwd
/home/user/
The output I am trying to achieve is something like:
spawn ssh user#myServer.com
Password:
You have new mail.
DISPLAY set to user.myServer:0.0
Current Directory is:
/home/user/
I've tried using log_user 0/1, stty etc.. but I can't seem to get it right with those...
Any help would be appreciated.
The problem is that the std output of the spawned process includes both program output and sent commands, the latter just because of echoing from the remote device.
You could manipulate stdout via the log_user command, turning it off while still expecting & capturing, and printing out the output yourself via the "puts" command. Lastly re-enable, if at all needed. The below works because Expect does not read the echoed command until the expect command.
I can't test now so I'll leave you the regexp to match the pwd output (beware of prompts with current paths), but since the point of your question is not the regexp, I figure the following will do for you:
#!/bin/bash
/usr/bin/expect << SSHLOGIN
set timeout 600
spawn ssh user#myServer.com
expect {
"Are you sure you want to continue connecting (yes/no)?" {
send "yes\n";exp_continue
}
Password: {
send "$2\n"
}
}
expect {
"# " {
send "pwd \n"
log_user 0
expect -re "(include_here_between_the_parenthesis_a_regexp_that_matches_your_pwd_output)" {
puts "Current directory is: $expect_out(1,string)"
}
log_user 1
}
expect {
"# " {
send "exit \n"
}
}
wait
SSHLOGIN
As a last comment... why not change the top line to #!/usr/bin/expect and make this an expect script as opposed to a bash one with a here documnet (or whatever that was called)? It's almost pure expect code after all.
Let me know how that goes, and don't forget upvoting or marking the answer if it indeed helped. :-)

Resources