I am using expect within bash. I want my script to telnet into a box, expect a prompt, send a command. If there is a different prompt now, it has to proceed or else it has to send that command again.
My script goes like this:
\#!bin/bash
//I am filling up IP and PORT1 here
expect -c "
set timeout -1
spawn telnet $IP $PORT1
sleep 1
send \"\r\"
send \"\r\"
set temp 1
while( $temp == 1){
expect {
Prompt1 { send \"command\" }
Prompt2 {send \"Yes\"; set done 0}
}
}
"
Output:
invalid command name "while("
while executing
"while( == 1){"
Kindly help me.
I tried to change it to while [ $temp == 1] {
I am still facing the error below:
Output:
invalid command name "=="
while executing
"== 1"
invoked from within
"while [ == 1] {
expect {
This is how I'd implement this:
expect -c '
set timeout -1
spawn telnet [lindex $argv 0] [lindex $argv 1]
send "\r"
send "\r"
expect {
Prompt1 {
send "command"
exp_continue
}
Prompt2 {
send "Yes\r"
}
}
}
' $IP $PORT1
use single quotes around the expect script to protect expect variables
pass the shell variables as arguments to the script.
use "exp_continue" to loop instead of an explicit while loop (you had the wrong terminating variable name anyway)
The syntax for while is "while test body". There must be a spce between each of those parts which is why you get the error "no such command while)"
Also, because of tcl quoting rules, 99.99% of the time the test needs to be in curly braces. So, the syntax is:
while {$temp == 1} {
For more information see http://tcl.tk/man/tcl8.5/TclCmd/while.htm
(you probably have other problems related to your choice of shell quotes; this answer addresses your specific question about the while statement)
Related
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.
I tried posting this in the codereview community, but there is no expect tag and I don't have enough karma to create tags.
I have written an expect script to either login to a server or run a simple (usually single) command and return the output.
I have two problems and a wish.
Commands that return nothing--i.e., ssh2server user host false--time out with an error (because I'm not capturing a timeout, though I suppose I should) instead of just returning nothing.
I can capture the return code of the program but I can't get it to exit with the appropriate code.
Is there a way I can take the output of the called program and return it the same way (remote stdout goes to local stdout and remote stderr goes to local stderr)?
Also, any comments or (constructive) criticisms would be appreciated.
#!/usr/bin/expect -f
if {[info exists ::env(SSH2SERVER_PASSWORD)]} {
set password "$env(SSH2SERVER_PASSWORD)"
} else {
puts "SSH2SERVER_PASSWORD not set"
exit 1
}
if {[llength $argv] < 2} {
puts "usage: ssh2server user server"
exit 1
}
set user [lindex $argv 0]
set server [lindex $argv 1]
set command [lrange $argv 2 end]
set pwd_prompt "*assword:"
set prompt "\$ "
set rc 0
expect_before {
#timeout { send_user 'timeout' ; exit 2 }
timeout { send_user 'timeout' ; set rc 2}
}
log_user 0
spawn ssh $user#$server
expect "$pwd_prompt" { send -- "$password\r" }
if { $command == "" } {
interact
} else {
expect {
"$prompt" {
send -- "PROMPT_COMMAND=\rPS1='_MYPROMPT_'\r$command\r"
#expect -re "$command\r\n(.*)\r\n\[^\r]*\[#\$%]"
expect -re "$command\r\n(.*)\r\n\[^\r]*_MYPROMPT_"
set results $expect_out(1,string)
puts $results
send -- "^D"
expect eof
#catch wait ec
#set rc [lindex $ec 3]
#puts [lindex $ec 3]
#exit [lindex $ec 3]
}
#eof { send_user $expect_out(buffer); exit 3}
eof { send_user $expect_out(buffer); set rc 3}
}
}
log_user 1
lassign [wait] pid spawnid os_flag rc
#puts $rc # outputs correct value
exit $rc
I suspect this is the problem:
send -- "^D"
You are not sending a Ctrl-D, you are sending the characters ^ and D.
To send a Ctrl-D
send -- "\04"
To solve the "no output, timeout" problem, you need to alter your expected regex: you have too many newlines for that case. Using expect -d would have revealed this to you. Like this:
send -- "unset PROMPT_COMMAND; PS1='_MYPROMPT_'\r"
expect -re "_MYPROMPT_$"
send -- "$command\r"
expect -re "$command(.*)\r\n_MYPROMPT_$"
The content of the capturing parentheses may now be empty.
I split off setting the prompt for clarity.
To capture the exit status of the command, you may have to do this:
send -- "$command; echo $?\r"
expect -re "$command(.*)\r\n(\d+)\r\n_MYPROMPT_$"
set results [regsub {^\r\n} $expect_out(1,string) ""]
set status $expect_out(2,string)
I don't think you can separate stdout and stderr with the expect command. I think both streams are captured as "output". (I don't have my Exploring Expect book nearby to confirm)
If that's important, you might want to invoke the command redirecting stdout and/or stderr to file(s), and then cat and capture the file contents.
I am not quite sure if it a good idea to create a new Thread or "reopen" this 3-year old Thread, if you think it's better to reopened it, please excuse my spam in this Forum but I have the same Problem and don't get a solution out of the information of the thread. At the Moment I had a script that looks like this:
#!/usr/bin/expect -f
set pass [lindex $argv 0]
spawn <CMD>
expect {
-re "Password: " {
send "$pass\r"
}
expect eof
catch wait result
}
exit [lindex $result 3]
but wenn I execute this Script I get the error
can't read "result": no such variable
while executing
"lindex $result 3"
invoked from within
"exit [lindex $result 3]"
(file "./call_tests.exp" line 13)
I already found out that probably the problem is that the SSH-Session is already closed when I try to read the exit code. But I started yesterday to understand the expect-command so I am a complete newbie and I would very thanks full if someone can help me out.
best regards
Dan
You should write like this:
expect {
-re "Password: " {
send "$pass\r"
}
}
expect eof
catch wait result
exit [lindex $result 3]
or this:
expect {
-re "Password: " {
send "$pass\r"
exp_continue
}
eof {
catch wait result
}
}
exit [lindex $result 3]
According to expect's man page:
expect [[-opts] pat1 body1] ... [-opts] patn [bodyn]
waits until one of the patterns matches the output of a spawned
process, a specified time period has passed, or an end-of-file
is seen. If the final body is empty, it may be omitted.
Patterns from the most recent expect_before command are implicitly used before any other patterns. Patterns from the most
recent expect_after command are implicitly used after any other
patterns.
If the arguments to the entire expect statement require more
than one line, all the arguments may be braced into one so as
to avoid terminating each line with a backslash. In this one
case, the usual Tcl substitutions will occur despite the braces.
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).
I'm including simple Expect commands within a Bash script (I know I could be just writing a pure Expect script, but I would like to get it to work from within Bash).
The script is below:
#!/bin/bash
OUTPUT=$(expect -c '
spawn ssh mihail911#blah.org
expect "password:"
send "dog\r"
')
Upon ssh'ing to the above address, it will return something of the form mihail911's password: on the prompt, so I think my expect line is valid.
When I run this my script does not print anything. It does not even show the password: prompt. In general, even if I manually provide an incorrect password, I will receive a Incorrect password-type response prompt. Why is nothing printing and how can I get my script to execute properly?
I have tried debugging by using the -d flag and it seems to show that at least the first expect prompt is being matched properly.
In addition, what values should I expect in the OUTPUT variable? When I echo this variable, it simply prints the first the first command of the expect portion of the script and then mihail911's password:. Is this what it's supposed to be printing?
Use:
#!/bin/bash
OUTPUT=$(expect -c '
# To suppress any other form of output generated by spawned process
log_user 0
spawn ssh dinesh#xxx.xxx.xx.xxx
# To match some common prompts. Update it as per your needs.
# To match literal dollar, it is escaped with backslash
set prompt "#|>|\\$"
expect {
eof {puts "Connection rejected by the host"; exit 0}
timeout {puts "Unable to access the host"; exit 0;}
"password:"
}
send "root\r"
expect {
timeout {puts "Unable to access the host"; exit 0;}
-re $prompt
}
send "date\r"
# Matching only the date cmd output alone
expect {
timeout { puts "Unable to access the host";exit 0}
-re "\n(\[^\r]*)\r"
}
send_user "$expect_out(1,string)\n"
exit 1
')
echo "Expect's return value: $?"; # Printing value returned from 'Expect'
echo "Expect Output: $OUTPUT"
Output:
dinesh#MyPC:~/stackoverflow$ ./Meric
Expect's return value: 1
Expect Output: Wed Sep 2 09:35:14 IST 2015
dinesh#MyPC:~/stackoverflow$