Looping over lines from a file in expect - linux

New at scripting, beware...
I'm attempting to use this code that i put together from reading a couple blogs. The idea is for this script to read the IPs that i have saved on ips.txt and then run the code to ssh to the read IP using the credentials given, perform a couple send commands as described below, exit the ssh session, and repeat with the second line in the ips.txt file, which is a different IP, until it finishes the IPs list.
Note: the ips.txt file is a simple list of IP addresses as follows (no spaces between the IPs):
192.168.0.2
192.168.0.3
192.168.0.4
...
The spawn, expect, and send commands work fine. It also loops fine back up to the beginning of the code but it will NOT read the second IP in the ips.txt file; it will just read the first one again and perform the same steps over and over.
Please assist...
#!/usr/bin/expect
set timeout 180
set username admin
set password Changeme1
set fildes [open "ips.txt" r]
set ip [gets $fildes]
while {[string length $ip] != 1} {
spawn ssh $username#$ip
expect "password:"
send "$password\r"
expect ".mi"
send "show sw\r"
expect ".mi"
send "exit\r"
set ip [gets $fildes]
}
close $fildes

Let's try to boil the code down to something that minimally reproduces the problem. You say that the loop repeats with the same ip value, right? So let's remove the code interacting with the remote system:
#!/usr/bin/expect
set fildes [open "ips.txt" r]
set ip [gets $fildes]
while {[string length $ip] != 1} {
puts $ip
set ip [gets $fildes]
}
close $fildes
What happens when you run this?
I expect that the while condition will never be satisfied: you'll read the file, printing each line, then you print an inifinite number of blank lines. When you read past the last line of a file, you get the empty string as a result, not a string with length 1.
You most likely want
#!/usr/bin/expect
set timeout 180
set username admin
set password Changeme1
set fildes [open "ips.txt" r]
# the 2-argument form of `gets` returns -1 when it can't read another line
while {[gets $filedes ip] != -1} {
spawn ssh $username#$ip
expect "password:"
send "$password\r"
expect ".mi"
send "show sw\r"
expect ".mi"
send "exit\r"
expect eof ;# wait for the connection to close
}
close $fildes

Related

How to turn off stderr in Expect script or stop "Connection to ... closed" message after exiting?

So I have been trying to learn bash scripting with expect and I'm running into an annoying problem where even after I turn off output by setting log_user to 0, the logout message for when I exit the ssh server is still printed as "logout
Connection to blah.blah.blah closed". Going the hackish way and issuing the exit command inside the script to expect is undesirable, and neglects to print a new line in the terminal, leaving an unsightly terminal prompt behind. How do I suppress or turn off this connection closed message in my expect script? Here's a template of the code for my expect script:
#!/usr/bin/expect -f
#-f flag tells expect to read commands from file
# Set Password Prompt and Password
set PASSP "password:"
set PASSW "your_password_here\n"
# Set SFTP Prompt
set SFTP "sftp> "
# Set Remote Prompt and Project Directories
# The GL server is super weird, so make sure RPROMPT ends in the
# name of the project compilation directory and PROJDIR is the
# relative path (from home but without the tilde) of the main
# project directory.
set RPROMPT "projdir_here]"
set PROJDIR "path_to_projdir_here"
# Set SSH Address
set ADDRESS your_username_here#your_address_here
# Annoying long output turned off
log_user 0
send_user "Logging in...\n"
spawn sftp $ADDRESS
expect $PASSP
send $PASSW
expect $SFTP
send_user "Uploading necessary files...\n"
send "cd $PROJDIR\n"
expect $SFTP
send "put *.cpp .\n"
expect $SFTP
send "put *.h .\n"
expect $SFTP
send "put Makefile .\n"
expect $SFTP
send "exit\n"
spawn ssh $ADDRESS
expect $PASSP
send $PASSW
expect "]"
send "cd $PROJDIR\n"
expect $RPROMPT
send_user "Cleaning remote directory...\n"
send "make clean\n"
# Output turned on temporarily so compiler feedback can be determined
expect $RPROMPT
log_user 1
send_user "Compiling on server...\n"
send "make\n"
# Turn off output and submit (this assumes that your makefile has a 'submit'
# target with the command to submit your file there)
expect $RPROMPT
log_user 0
send_user "\nSubmitting Requisite Files...\n"
send "make submit\n"
expect $RPROMPT
send_user "Quitting...\n"
# I can't figure out how to turn off stderr in expect so that an annoying 'connection closed'
# message is printed even the log_user has been set to 0
send "logout\n"
# Transfer control to user
interact
You can see that at the end of the program, before I use the interact command, that the offending message is printed after "logout\n" is sent and the connection to the server is closed. This happens despite log_user being set to 0 above. How on earth do I turn off stderr output inside my script? I've even looked at the manual pages for expect at expect man page but there didn't seem to be much useful there. I don't understand how to turn off stderr inside an expect script so that this message isn't printed, help would be much appreciated! I really just want the script to be quiet when I'm logging out. Because it's pretty obvious that I issued a logout command, I'm not sure why this output goes to stderr. Is there a command in ssh that won't output a "Connection to...closed" message when the user exits? It would be better if I could just pipe stderr to oblivion or suppress it somehow. The normal ways don't seem to work because this is an expect script.
It is just the incorrect use of log_user. It should be used as,
log_user 0
send "logout\r"
expect eof
log_user 1
Note that I am expecting for eof pattern after sending logout\r, because it will lead to the closure of the connection. Always use \r in place of \n while using the send command.
Read here to know more about the log_user to suppress the output.
Instead of having a prompt to defined as a static string, it can be generalized as,
# We escaped the `$` symbol with backslash to match literal '$'
# The last dollar sign represents line-end.
set prompt "#|>|%|\\\$ $";
While the expect is used, we have to accompany with -re flag to specify that as a regular expression.
expect -re $prompt
SshDemo.exp
#!/usr/bin/expect
set prompt "#|>|\\\$ $"
spawn ssh dinesh#myserver
expect {
"(yes/no)" { send "yes\r";exp_continue}
"password"
}
send "welcome123\r"
expect -re $prompt
# Simply executing 'ls -l' command...
send "ls -l\r"
expect -re $prompt
log_user 0
send "logout\r"
expect eof
log_user 1
Here's my new, improved script based on the suggestions above. I also decided to use scp instead of sftp because it's much more concise and I'm only uploading files.
#!/usr/bin/expect -f
#-f flag tells expect to read commands from file
# Set Password Prompt and Password
set PASSP "password: "
set PASSW "your_password_here"
# Set Remote Prompt and Project Directories
# The ssh server is super weird, so make sure PROJDIR is the
# directory name of the main project directory and PATH is
# the relative path to the project directory from home (with the tilde)
set PROJDIR "your_projdir_name_here"
set PATH "your_projdir_path_here"
# Set SSH Address
set ADDRESS your_username_here#source_hostname_here
# Turn off annoying output
log_user 0
send_user "Logging in and uploading necessary files...\n"
# upload files via scp, (shows upload prompt for user, concise)
spawn bash -c "scp *.cpp *.h Makefile $ADDRESS:$PATH"
expect $PASSP
send "$PASSW\r"
interact
spawn ssh $ADDRESS
expect $PASSP
send "$PASSW\r"
expect "]"
send "cd $PATH\r"
expect "$PROJDIR]"
send_user "\nCleaning remote directory...\n"
send "make clean\r"
# Output turned on temporarily so compiler feedback can be determined
expect "$PROJDIR]"
log_user 1
send_user "Compiling on server...\n\n"
send "make\r"
# Turn off output and submit (this assumes that your makefile has a 'submit'
# target with the command to submit your file there)
expect "$PROJDIR]"
log_user 0
send_user "\n\nSubmitting Requisite Files...\n"
send "make submit\r"
expect "$PROJDIR]"
send_user "Quitting...\n"
send "logout\r"
expect eof
# Exit
exit

Loop a function with specific condition

I have a function:
set timeout 20
set f [open "password.txt"]
set password [read $f]
close $f
spawn ssh user#example.net -p 724
expect "user#example.net's password:"
send $password
interact
in the password.txt I have only 1 word
After running this script using expect in terminal I have the access to the server if that single word that exist there is the right one.
I have 2 problems:
SOLVED---------1)I want to loop the execution of this script in such a way that in case I will have 2,5,100 words in password.txt the script will try all of them for password, not only the first one as it is now.
2)If I will manage to loop the execution of the function using all words from the
password.txt, I need to make on if statement that will solve this issue(after 3 attempts to write the password server disconnects you so you need to connect again after each 3 attempts.)
Update: the server answer after first 2 attempts like this Permission denied, please try again. after the last attempt (3th one) it's says Permission denied (publickey,password). and it throw's me to my normal command line state.
I am trying to do something like
while read line
do
echo "Trying: $line"
echo ssh user#example.net -p 724 --password "$line" ## NOT REAL CODE
done < password.txt
but have no idea how to implement this logic in the expect syntax.
Till now I managed to write this
set timeout 20
set f [open "password.txt" r]
set password [read $f]
close $f
foreach i $password {
puts "trying this as a pass : $i"
spawn ssh user#example.net -p 724
expect "user#example.net's password:"
send $i
interact
}
It solves me first problem but the bug is that I need to press manaul "enter" after the server asks for the password. And after I hit "enter" the script tries first word from the list, then if it's wrong one again I need to press "enter" and the script will try the second word from the list.
How can I simulate pressing "enter" in the script?

Tcl expects output from tclsh to shell script

I'm searching for last few days for solution to my problem so finally I came here.
I got to write a Tcl script that should be opened from a Linux/Debian system. The problem is, that the script should log into a Cisco router and then go into the special tclsh mode, where it should get the SNMP value which represents txload. After a moment I get the value in tclsh mode (which supports SNMP), but I can't get it back to my script. It still exists only in router tclsh mode. My script should change some other values depending on the value I already have in tclsh, but tclsh is not needed anymore.
So here's my question: How can I get the $ifnum value from tclsh but I could still use in that script for some loops, because I need to make some loops where the $ifnum will be the condition?
As you can see, the script is not finished yet, but as I said, I'm stuck here and trying to figure it out:
#!/usr/bin/expect -f
set gateway [lindex $argv 0]
set serial [lindex $argv 1]
set timeout 5
spawn telnet $gateway
expect "Password:"
send "cisco\r"
expect "*>"
send "en\r"
expect "Password:"
send "cisco\r"
expect "*#"
send "set ifnumstr \[snmp_getone odczyt 1.3.6.1.4.1.9.2.2.1.1.24.6\]\r"
expect "*}"
send "regexp \{val='(\[0-9\]*)'\} \$ifnumstr \{\} ifnum \r"
expect "*#"
send "puts \$ifnum \r"
expect "*#"
I'd make it print out the thing you're looking for between markers, and then use expect in its RE matching mode to grab the value.
send "puts \"if\\>\$ifnum<if\"\r"
expect -re {if>(.*?)<if}
# Save the value in a variable in the *local* script, not the remote one
set ifnum $expect_out(1,string)

parse information from expect command

I need to parse resulting data from a telnet/ssh command and act on the data.
As an example, I want to interact with a spawn session (ssh here), list files in current dir and collect only file of a certain extension to later execute a command on those files only.
What I've got so far:
#!/usr/bin/expect
set timeout 3
match_max 10000
set prompt {$ }
spawn ssh $user#$host
expect "password: "
send $pw\r
expect $prompt
# here's the command I need to parse resulting data
send "ls -1\r"
expect -re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
That script opens a ssh session, sends the command and displays all the files in current dir (log and others) but I need to process each file matching a given pattern, how can I do this?
script output:
$ DATA:
0_system.log
1_system.log
2_system.log
3_system.log
a.log
a.sh
blah
b.sh
data.csv
First of all... wouldn't ls -1 *.log be easier, and do the trick?
That said, I have found that usually you have to be very careful when using (.*), it can have at times unexpected results.
I would go with "any alphanumeric character plus underscore" (if that works for your filenames) - see my suggested expect block below. Also, keep in mind that by using $expect_out(1,string) you are saving only the filename, without the extension - not sure if that is what you want. If you want the whole thing, $expect_out(0,string) is the way to go in this case.
This will do it:
expect -re "(\[a-zA-Z0-9_\]*)\.log" {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
Hope that helps!
James
Corrected Expect probably should be:
expect {
-re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue;
}
}

Using if/else in expect script

I recently updated and reformatted my /etc/hosts file and would like to run through every system (roughly 1000) to refresh my known_hosts file. I'm looking at using an expect script to send "yes" to the RSA fingerprint question. Simple enough. However, I know some of these systems are completely new to me and my password has not been set. This creates two possibilities:
"yes" is sent to the RSA fingerprint question and I'm logged into
the server. I'll then need to send an exit to close the connection
before moving onto the next host. Or...
"yes" is sent to the RSA fingerprint question and I'm presented with
the prompts to update my password starting with the current and
followed by the new password entered twice. The connection will
automatically close after the password is updated moving onto the
next host.
I think I have a basic grasp of the concept of "if/else" in expect, but I don't fully understand how to nest them, if there is a better way, or if I'm completely off-base to begin with.
This is what I have right now:
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/user/.pw1.txt]
set pw2 [exec cat /home/user/.pw2.txt]
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho "ssh $host"
expect {
"continue connecting"{
send "yes\r"
expect {
"current" {
send $pw2\r
} "New password" {
send $pw1\r
} "Retype new password" {
send $pw1\r
}
}
expect "msnyder"
send "exit\r"
}
interact
}
The file1 variable is the list of hosts to run the script against.
I know it isn't accurate because it errors on line 22. But, I have no idea what needs to be fixed.
Two errors I spotted:
missing close brace, probably for the "continue connecting" block
missing space before the open brace of "continue connecting". Tcl (hence Expect) is very sensitive to whitespace as it is parsed into words before the commands are evaluated. For the very few gory details, see the 12 syntax rules of Tcl.
Your code might look like:
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho "ssh $host"
expect {
"continue connecting" {
send "yes\r"
expect {
"current" {
send -- $pw2\r
exp_continue
}
"New password" {
send -- $pw1\r
exp_continue
}
"Retype new password" {
send -- $pw1\r
exp_continue
}
msnyder
}
send "exit\r"
}
}
interact
}
Notes:
exp_continue is used to "loop" back up to the expect statement: in this case, you will expect to see all of "current", "new" and "retype", so you don't want to bail out until you see your prompt.
get into the habit of typing send -- something. Without the double dash, you'll be surprised the day someone types in a password with a leading dash.
You can probably avoid having the script run like a human and just spawn an expected to fail ssh connection which will automatically accept the host RSA key and not bother with prompting for a password; you can do that later for new systems where you need to initiate a password.
Temporarily try adding this to your ~/.ssh/config file until your script is finished:
Host *
Protocol 2
PasswordAuthentication 0
StrictHostKeyChecking 'no'
CheckHostIP 'no'
UserKnownHostsFile ~/.ssh/known_hosts_new
Then when the new know_hosts_new file is loaded up, you can replace the default ~/.ssh/known_hosts and remove the UserKnownHostsFile line from your config.

Resources