Using expect to SSH to server and execute commands - tcl

I want to write a generic expect script to login through SSH to a system and execute some commands. An example I found had the following:
#!/usr/bin/expect
set fid [open ./.secret]
set password [read $fid]
close $fid
spawn /usr/bin/ssh root#[lindex $argv 0]
expect {
-re ".*Are.*.*yes.*no.*" {
send "yes\n"
exp_continue
#look for the password prompt
}
"*?assword:*" {
send $password
send "\n"
}
}
send -- "PS1='>'\r"
expect -re ">$" { send "hostname\r" }
expect -re ">$" { send "pwd\r" }
...the script seems to login properly but it didn't execute the last 2 sends. Ideas?
Edit:
After enabling exp_internal, I noticed the following:
expect: does "" (spawn_id exp4) match glob pattern "*"? yes
expect: set expect_out(0,string) ""
expect: set expect_out(spawn_id) "exp4"
expect: set expect_out(buffer) ""
send: sending "PS1='>'\r" to { exp4 }
Gate keeper glob pattern for '>$' is '>'. Activating booster.
expect: does "" (spawn_id exp4) match regular expression ">$"? Gate ">"? gate=no
expect: does "\r\n" (spawn_id exp4) match regular expression ">$"? Gate ">"? gate=no
Last login: Tue Nov 6 14:13:31 2012 from 1.x.x.x
expect: does "\r\nLast login: Tue Nov 6 14:13:31 2012 from 1.x.x.x\r\r\n" (spawn_id exp4) match regular expression ">$"? Gate ">"? gate=no
I'm trying to send PS1='>'\r because I want to override the prompt. I don't think there's any way for me to predict what the prompt will be and therefore, I wouldn't know what pattern to expect. From the above, it looks like the prompt wasn't changed. How do you tackle a problem like this?

There doesn't appear to be anything obviously wrong with your script (though using a different prompt might make matching a bit easier). Which means it is something subtle. I suggest adding this:
exp_debug 1
to somewhere early in your script. It will make the Expect engine print a lot more about what it is doing, which will (probably) help you understand what is going wrong, or failing that help the people here help you…

Related

How to get responses in expect (tcl)

I am trying to query bluetoothctl using expect (tcl), but I cannot seem to get the bluetoothctl responses saved to a variable for processing with tcl.
For example:
spawn bluetoothctl
exp_send "scan on\n"
expect {
-re {*NEW*} {
set new $expect_out(0,string)
puts "scan - found $new"
exp_continue
}
timeout {
exp_send "scan off\n"
exp_send "quit\n"
close
wait
puts "EXPECT timed out"
}
}
The result of the above is along the lines of:
[bluetooth]# scan on
Discovery started
[CHG] Controller 10:08:B1:57:35:62 Discovering: yes
[NEW] Device EB:06:EF:34:04:B7 MPOW-059
[bluetooth]#
EXPECT timed out
So nothing is output until expect is closed. I have been trying this all day with different combinations but - I am stuck. Any help would be appreciated. Thanks
Edit: changed the regex to (.NEW.) and that works. So now I get:
[bluetooth]# scan on
Discovery started
[CHG] Controller 10:08:B1:57:35:62 Discovering: yes
[NEW] Device EB:06:EF:34:04:B7 MPOW-059
[bluetooth]# scan - found scan on
Agent registered
[bluetooth]# scan on
Discovery started
[CHG] Controller 10:08:B1:57:35:62 Discovering: yes
[NEW
which is everything except the bit that I wanted to retrieve viz:
[NEW] Device EB:06:EF:34:04:B7 MPOW-059
That regular expression looks syntactically wrong. If you did {.*NEW.*} then it might work. Assuming that those three letters are actually being output by bluetoothctl with no control characters mixed in. (It'd be weird to do that, but some code is weird…)
Apart from that, have you tried the diagnostic mode for expect? Pass the -d flag to the expect program when you start it to get lots of output about what it is really seeing and looking for.
So the answer appears to be:
The expect_out(buffer) is cleared by a puts statement
Find all the possible responses expected making sure that the expected response specifies the whole line.
Save the buffer in a variable if required
Issue a puts statement to clear the buffer
So:
expect {
"Hello" {
puts "$expect_out(buffer)"
exp_continue
}
-re (How.*) {
set answer $expect_out(buffer)
if {$answer == "How are you"} {
exp_send "Well thank you"
}
}
or, in the example above:
expect {
"Discovery started" {
puts $expect_out(buffer)
exp_continue
}
-re (.CHG.*) {
puts $expect_out(buffer)
exp_continue
}
-re (.NEW.*) {
set new $expect_out(buffer)

Expect script file name too long when writing to a file

I'm new to coding and this is my first script. It works up to the point where it has to open(create) the file to write in. It then fails giving me an error somepassedNickvariable.txt file name too long. I run it as the user "qbot".
Sample output for the first expect:
2016-08-05T23:32:42 73600,565 INF Chat: 'Quadro': !ustawdomek
Sample output for the second one:
1. id=51890, Pepesza, pos=(473,1, 42,1, 1223,7), rot=(-66,1, 104,1, 0,0), remote=True, health=97, deaths=6, zombies=138, players=1, score=109, level=35, steamid=xxx, ip=xx.xx.xx.xx, ping=111
2. id=1141, Quadro, pos=(465,6, 87,1, -624,1), rot=(-30,9, 1620,0, 0,0), remote=True, health=187, deaths=1, zombies=525, players=0, score=520, level=84, steamid=xxx, ip=xx.xx.xx.xx, ping=24
Total of 6 in the game
Any help would be greatly appreciated as I can't get it to work by myself.
#!/usr/bin/expect
set timeout -1
spawn telnet localhost 8081
expect "Please enter password" {sleep 3; send "blahblah\r" }
while {1} {
expect \
{
"*INF Chat: '*': !ustawdomek" {
regexp {'(.+)':\s\!ustawdomek} $expect_out(buffer) match Nick;
set listplayers "*Total of * in the game";
send "lp\r"
expect $listplayers {
regexp "$Nick\,\\spos\=\\(\(\(\[-\]\?\\d+\)\,\\d\,\\s\(\[-\]\?\\d+\)\,\\d\,\\s\(\[-\]\?\\d+\)\,\\d\)\\)" $expect_out(buffer) match lok lok1 lok2 lok3
set file [open "/home/qbot/domki/$Nick.txt" w]
puts $file "$lok1 $lok2 $lok3"
close $file
send "pm $Nick \"\[QBOT\]Blahblah\"\r" }
}
timeout {break}
eof {break}
}
}
I don't entirely understand this script's syntax. But I guess it has something to do with the regexp {'(.+)':\s\!ustawdomek} $expect_out(buffer) match Nick;
.+ as such is a greedy regular expression and should be avoided as long as possible. I think it's trying to match a very large filename. Try to use a simpler regexp that allows only word matches and possibly limit the size of filename. For instance, \w{1,25} or something like that.

Expect script if command won't run

Can anyone help me with the following. I think it could be due to whitespace but for the life of me I can't figure it out.
This is supposed to be a simple script that will move a file depending on it's location. I'm using expect as I want it to be handled on my NAS (via ssh), rather than taking the file off and putting it back on just to move it between shares.
#!/usr/bin/expect -f
# Script to organise downloaded file
set FileDir [lindex $argv 0]
set FileName [lindex $argv 1]
set MiscDir "/media/Misc/Downloads"
set DownDir "/media/Downloads"
if { [string compare $FileDIR $DownDir] = 0 } {
} elseif { [string compare $FileDIR $MiscDir] = 0 } {
spawn ssh *****#*******
expect "assword:"
send "********\r"
expect "$ "
send "mv ~/Misc/Downloads/$FileNAME '~/Misc/To Convert/$FileNAME'"
expect "$ "
send "exit\r"
expect eof'
}
Updated Code:
#!/usr/bin/expect -f
# Script to organise downloaded file
set FileDir [lindex $argv 0]
set FileName [lindex $argv 1]
set MiscDir "/media/Misc/Downloads"
set DownDir "/media/Downloads"
if { [string compare $FileDir $DownDir] == 0 } {
} elseif { [string compare $FileDir $MiscDir] == 0 } {
set OrigFile "\"/shares/Misc/Downloads/$FileName\""
set MoveFile "\"/shares/Misc/To Convert/$FileName\""
spawn ssh Admin#Appledore
expect "assword:"
send "xxxxxxxx\r"
expect "$ "
send "mv $OrigFile $MoveFile"
expect "$ "
send "exit\r"
expect eof'
}
Debug Output
spawn ssh Admin#Appledore parent: waiting for sync byte parent: telling child to go ahead parent: now unsynchronized from child spawn: returns {5222}
expect: does "" (spawn_id exp6) match glob pattern "assword:"? no Admin#appledore's password: expect: does "Admin#appledore's password: " (spawn_id exp6) match glob pattern "assword:"? yes expect: set expect_out(0,string) "assword:" expect: set expect_out(spawn_id) "exp6" expect: set expect_out(buffer) "Admin#appledore's password:" send: sending "*******!\r" to { exp6 }
expect: does " " (spawn_id exp6) match glob pattern "$ "? no
expect: does " \r\n" (spawn_id exp6) match glob pattern "$ "? no [Admin#Appledore ~]$ expect: does " \r\n[Admin#Appledore ~]$ " (spawn_id exp6) match glob pattern "$ "? yes expect: set expect_out(0,string) "$ " expect: set expect_out(spawn_id) "exp6" expect: set expect_out(buffer) " \r\n[Admin#Appledore ~]$ " send: sending "mv "/shares/Misc/Downloads/NOOBS_lite_v1_4.zip" "/shares/Misc/To Convert/NOOBS_lite_v1_4.zip"" to { exp6 }
expect: does "" (spawn_id exp6) match glob pattern "$ "? no <ownloads/NOOBS_lite_v1_4.zip" "/shares/Misc/To Convert/NOOBS_lite_v1_4.zip" expect: does "mv "/shares/Misc/Downloads/NOOBS_lite_v1_4.zip" "/shares/Misc/To Convert/NOOBS_lite_v1_4.zip\r<ownloads/NOOBS_lite_v1_4.zip" "/shares/Misc/To Convert/NOOBS_lite_v1_4.zip" \u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008\u0008" (spawn_id exp6) match glob pattern "$ "? no
change your = to ==
= is usually for assignment (and doesn't work in tcl. as commented below)
== is test for equalness

linux - telnet - script with expect

I was writing an expect-script which communicate with a server via telnet, but right now i need to evaluate the reply from server.
Usage:
./edit.expect
EXPECT script:
#!/usr/bin/expect<br>
spawn telnet ip port
expect "HUH?"
send "login testuser pass\r"
expect "good"
send "select 1\r"
expect "good"
send "me\r"
expect "nick=testuser id=ID group=testgroup login=testuser"
send "edit id=ID group=3\r"
expect "good"
send "quit\r"
If i send the command "me" i get a reply from the server which i need to evaluate.
The reply from server looks like this example... "nick=NICK id=ID group=GROUP login=LOGIN".
How do i extract the id of the reply and use it in a send-command?
I hope you could help me with that. Thanks a lot!
You can try this way too.
set user_id {}
expect -re {nick=(.*)\s+id=(.*)\s+group=(.*)\s+login=(.*)\n} {
#Each submatch will be saved in the the expect_out buffer with the index of 'n,string' for the 'n'th submatch string
puts "You have entered : $expect_out(0,string)"; #expect_out(0,string) will have the whole expect match string including the newline
puts "Nick : $expect_out(1,string)"
puts "ID : $expect_out(2,string)"
puts "Group : $expect_out(3,string)"
puts "Login : $expect_out(4,string)"
set user_id $expect_out(2,string)
}
send "This is $user_id, reporting Sir! ;)"
#Your further 'expect' statements goes below.
You can customize the regexp as per your wish and note the use of braces {} with -re flag in the expect command.
If you are using braces, Tcl won't do any variable substitution and if you need to use variable in the expect then you should use double quotes and correspondingly you need to escape the backslashes and wildcard operators.
expect lets you match the incoming strings with regular expressions and get the submatches in the expect_out() array. In your example, you could use
send "me\r"
expect -re {nick=([^ ]*) id=([^ ]*) group=([^ ]*) login=([^ ]*)}
set nick $expect_out(1,string)
set id $expect_out(2,string)
set group $expect_out(3,string)
set login $expect_out(4,string)
puts "GOT nick: $nick id: $id group: $group login: $login"
send "edit id=$id group=3\r"
etc...
EDIT: string must be in {} to avoid command expansion

Problems looping to prompt for another password

I need some help with an EXPECT script please....
I'm trying to automate a login, prior to accessing a load of hosts, and cater for when a user enters a password incorrectly. I am getting the username and password first, and then validating this against a particular host. If the password is invalid, I want to loop round and ask for the username and password again.
I am trying this :-
(preceding few irrelevant lines omitted)
while {1} {
send_user "login as:- "
expect -re "(.*)\n"
send_user "\n"
set user $expect_out(1,string)
stty -echo
send_user "password: "
expect -re "(.*)\n"
set password $expect_out(1,string)
stty echo
set host "some-box.here.there.co.uk"
set hostname "some-box"
set host_unknown 0
spawn ssh $user#$host
while {1} {
expect {
"Password:" {send $password\n
break}
"(yes/no)?" {send "yes\n"}
"Name or service not known" {set host_unknown 1
break}
}
}
if {$host_unknown < 1} {
expect {
"$hostname#" {send "exit\r"
break
}
"Password:" {send \003
expect eof
close $spawn_id
puts "Invalid Username or Password - try again..."
}
}
} elseif {$host_unknown > 0} {
exit 0}
}
puts "dropped out of loop"
And now I can go off and do lots of stuff to lots of boxes .....
This works fine when I enter a valid username or password, and my script goes off and does all the other stuff I want, but when I enter an invalid password I get this :-
Fred#Aserver:~$ ./Ex_Test.sh ALL
login as:- MyID
password: spawn ssh MyID#some-box.here.there.co.uk
Password:
Password:
Invalid Username or Password - try again...
login as:- cannot find channel named "exp6"
while executing "expect -re "(.*)\n""
invoked from within "if {[lindex $argv 1] != ""} {
puts "Too many arguments"
puts "Usage is:- Ex_Test.sh host|ALL"
} elseif {[lindex $argv 0] != ""} {
while {1} {
..."
(file "./Ex_Test.sh" line 3)
Its the line "can not find channel named "exp6" which is really bugging me.
What am I doing wrong? I am reading Exploring Expect (Don Lines) but getting nowhere....
Whenever expect is supposed to wait for some word, it will save the spawn_id for that expect process into expect_out(spawn_id).
As per your code, expect's spawn_id is generated when it encounters
expect -re "(.*)\n"
When user typed something and pressed enter key, it will save the expect's spawn_id. If you have used expect with debugging, you might have seen the following in the debugging output
expect does "" (spawn_id exp0) match regular expression "(.*)\n"
Lets say user typed 'Simon', then the debugging output will be
expect: does "Simon\n" (spawn_id exp0) match regular expression "(.*)\n"? Gate "*\n"? gate=yes re=yes
expect: set expect_out(0,string) "Simon\n"
expect: set expect_out(1,string) "Simon"
expect: set expect_out(spawn_id) "exp0"
expect: set expect_out(buffer) "Simon\n"
As you can see, the expect_out(spawn_id) holds the spawn_id from which it has to expect for values. In this case, the term exp0 pointing the standard input.
If spawn command is used, then as you know, the tcl variable spawn_id holds the reference to the process handle which is known as the spawn handle. We can play around with spawn_id by explicitly setting the process handle and save it for future reference. This is one good part.
As per your code, you are closing the ssh connection when wrong password given with the following code
close $spawn_id
By taking advantage of spawn_id, you are doing this and what you are missing is that setting the expect's process handle back to it's original reference handle. i.e.
While {1} {
###Initial state. Nothing present in spawn_id variable ######
expect "something here"; #### Now exp0 will be created
###some code here ####
##Spawning a process now###
spawn ssh xyz ##At this moment, spawn_id updated
###doing some operations###
###closing ssh with some conditions###
close $spawn_id
##Loop is about to end and still spawn_id has the reference to ssh process
###If anything present in that, expect will assume that might be current process
###so, it will try to expect from that process
}
When the loop executes for the 2nd time, expect will try to expect commands from the spawn_id handle which is nothing but ssh process which is why you are getting the error
can not find channel named "exp6"
Note that the "exp6" is nothing but the spawn handle for the ssh process.
Update :
If some process handle is available in the
spawn_id, then expect will always expect commands from that
process only.
Perhaps you can try something like the following to avoid these.
#Some reference variable
set expect_init_spawn_id 0
while {1} {
if { $expect_spawn_id !=0 } {
#when the loop enters from 2nd iteration,
#spawn_id is explicitly set to initial 'exp0' handle
set spawn_id $expect_init_spawn_id
}
expect -re "(.*)\n"
#Saving the init spawn id of expect process
#And it will have the value as 'exp0'
set expect_init_spawn_id $expect_out(spawn_id)
spawn ssh xyz
##Manipulations here
#closing ssh now
close $spawn_id
}
This is my opinion and it may not be the efficient approach. You can also think of your own logic to handle these problems.
You simply need to store the $spawn_id as a temp variable before a nested expect command, then set the $spawn_id to the temp variable after a nested expect command.
Also, get rid of the while {1} loops. They are not needed because expect behaves like a loop provided you use exp_continue whenever you don't wish to exit. You don't need expect eof nor do you need close $spawn_id. I don't use them in the following example:
#!/usr/bin/expect
set domain [lindex $argv 0];
set timeout 300
spawn ./certbot-add.sh $domain
expect {
"*replace the certificate*" {
send "2\r";
exp_continue;
}
"*_acme-challenge*" {
puts [open output.txt w] $expect_out(buffer)
spawn ./acme-add.sh $domain
set tmp_spawn_id $spawn_id
expect {
"$ "
}
set spawn_id $tmp_spawn_id
send "\r";
exp_continue;
}
"*certificate expires on*" {
puts "Certificate Added!"
}
}