expect for ssh passwordless - bash

i have a file that has ip addresses, locations of files and passwords. I cannot use ssh key authentification, that's why i'm limited to use expect inside a bash script. the file contain the information as follow, seperated by spaces:
ip location password
ip location password
etc
my script is :
VAR=$(expect -c "
set fid [open "file.conf" w]
set content [read $fid]
close $fid
set records [split $content "\n"]
foreach rec $records {
set fields [split $rec]
lassign $fields\ ip location password
puts "$ip"
puts "$location"
puts "$password"}
spawn ssh $ip tail -f $location > /home/log_$ip 2>/dev/null &
expect {
".*Are.*.*yes.*no.*" { send "yes\n" }
"*?assword:*" { send "$password\r" }
}
")
echo "$VAR"
when i run the script, it gives me this error :
wrong # args: should be "read channelId ?numChars?" or "read ?-nonewline? channelId"
while executing
"read "
invoked from within
"set content [read ]"

You need to enclose the expect body in single quotes, so the expect variables are not expanded by the shell before expect starts to execute.
Also, if you hit the "are...yes...no" prompt, then you need to use exp_continue so you can keep expecting the password prompt.
DON'T OPEN THE CONF FILE WITH "w" -- you will destroy the file contents. You are reading from it, so open it for reading
VAR=$(expect -c '
set fid [open "file.conf" r]
while {[gets $fid line] != -1} {
lassign [split $line] ip location password
puts "$ip"
puts "$location"
puts "$password"
spawn ssh $ip tail -n 20 $location > /home/log_$ip 2>/dev/null &
expect {
-re "Are.*yes.*no" { send "yes\r"; exp_continue }
-gl "*?assword:*" { send "$password\r" }
}
}
')
I'm not sure if this will work when you redirect spawn output: I'm worried that expect will the have nothing to work with. If this doesn't work, remove "> /home/log_$ip 2>/dev/null &" or use
spawn ssh $ip tail -n 20 $location 2>/dev/null | tee /home/log_$ip
For backgrounding, you probably have to do something like this (untested)
expect -c '...' > /home/log_$ip 2>&1 &
expect_pid=$!
# ...
# later
wait $expect_pid
VAR=$(< /home/log_$ip)
do something with "$VAR"

Related

Inserting text through ssh without multiple password entries

I'm trying to setup a DNS server through ssh and am able to send text to the config files, but it requires a password for each line.
ssh -t $newDNS "sudo sed -i '4iforwarders { $IpDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^forwarders/i listen-on port 53 { $IpDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^listen-on/i allow-query { localhost; $subDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^forwarders/a recursion yes; }/etc/bind/named.conf.options"
I think you either need to install a ssh key onto each server or use expect and type your password once.
The ssh key solution:
Read this link, but do not set up a password for your RSA key.
The expect solution:
#!/bin/bash
# This function is sent to the server. I've used it for testing
function dosomething {
ls
}
# The script expects a file with servers separated with newlines.
FILE=$1
# If the file does not exist, it exits.
[ -f "${FILE}" ] || exit 1
# I didn't know your password and username. If you would like me to add it,
# please sent me a DM or paste it as comment ;-)
read -sp "username: " USERNAME
echo
read -sp "password: " PASSWORD
echo
# While the content of ${FILE} isn't empty...
while IFS= read -r SERVER; do
# .. use expect to spawn ssh, login with ${PASSWORD}, send the dosomething to
# the server, execute dosomething and exit.
expect <<-EOF
spawn ssh ${USERNAME}#${SERVER}
expect "*: " { send "${PASSWORD}\r" }
expect "*$ " { send "$(typeset -f dosomething)\r" }
expect "*$ " { send "dosomething\r" }
expect "*$ " { send "exit\r" }
EOF
done < ${FILE}
# You're up ;-)
exit $?

Expect within bash script

I am trying to implement an expect script into a bash script. Bear with me since I am fairly new to bash/expect.
Here is the expect script that works as intended:
log_user 0
file delete foo.txt
set fh [open foo.txt a]
set servers {xxx#server1 xxx#server2}
foreach s $servers {
spawn ssh $s
expect "password: "
send "PASSWORD\r"
expect "$ "
send "grep "something" /some/log/file.log"
expect "$ " { puts $fh "$expect_out(buffer)"}
send "exit\r"
}
close $fh
Now, I am hoping to include this expect script in a bash script but it is not working as intended.
Here is what I have so far:
#!/bin/bash
XYZ=$(expect -c "
file delete foo.txt
set fh [open foo.txt a]
set servers {xxx#server1 xxx#server2}
foreach s $servers {
spawn ssh $s
expect "password: "
send "PASSWORD\r"
expect "$ "
send "grep "something" /some/log/file.log"
expect "$ " { puts $fh "$expect_out(buffer)"}
send "exit\r"
}
close $fh
")
echo "$XYZ"
The error I am getting is:
command substitution: line 42: syntax error near unexpected token `('
command substitution: line 42: `expect "$ " { puts $fh "$expect_out(buffer)"}'
I'm open to any other ways to implement this! :)
You can use /usr/bin/expect -c to execute expect commands :
#!/bin/bash
/usr/bin/expect -c '
file delete foo.txt
set fh [open foo.txt a]
set servers {xxx#server1 xxx#server2}
foreach s $servers {
spawn ssh $s
expect "password: "
send "PASSWORD\r"
expect "$ "
send "grep "something" /some/log/file.log"
expect "$ " { puts $fh "$expect_out(buffer)"}
send "exit\r"
}
close $fh
'
Bertrand's answer is one way to solve your question, but does not explain the problem with what you were doing.
Bash attempts to expand variables inside double quoted strings, so your expect script will see blank spaces where you want to see $servers, $s, and $fh. Further, you have triply-nested sets of double-quoted strings going on that will cause all sorts of problems with parsing the arguments to expect.
It's a matter of opinion, but I think when something gets to a certain point where it's considered a program of its own, it should be separated into a separate file.
#!/usr/bin/expect
log_user 0
file delete foo.txt
set fh [open foo.txt a]
set servers {xxx#server1 xxx#server2}
foreach s $servers {
spawn ssh $s
expect "password: "
send "PASSWORD\r"
expect "$ "
send "grep 'something' /some/log/file.log"
expect "$ " {
puts $fh "$expect_out(buffer)"
}
send "exit\r"
}
close $fh
Make sure it's executable, and then call it from your bash script:
#!/bin/bash
/usr/local/bin/my_expect_script
(To do this really properly, you should set up public key authentication and then you can get rid of expect altogether by running ssh server "grep 'something' /some/log/file.log" directly from bash)

[Expect Script]Separate log files for each device

My code is generated for one log file output from multiple devices.
How could we separate each device for each log file output?
Here is my code:
#!/usr/bin/expect -f
#Slurp up the input file
set fp [open "ip.txt" r]
# To avoid empty lines, 'nonewline' flag is used
set file_data [read -nonewline $fp]
close $fp
set prompt ">"
log_file -noappend router_status.txt
foreach ip [split $file_data "\n"] {
puts "Router $ip Interface Status"
spawn telnet $ip
expect "Username:"
send "username\r"
expect "assword:"
send "password\r"
expect $prompt
# To avoid sending 'Enter' key on huge configurations
send "show interface description\r"
expect {
-ex "---(more" { send -- " "; exp_continue }
"*>" { send "exit\r" }
}
set timeout 3; # Reverting to default timeout
# Sending 'exit' at global level prompt will close the connection
expect eof
}
You can simply achieve it by changing the log_file
foreach ip [split $file_data "\n"] {
puts "Router $ip Interface Status"
spawn telnet $ip
# Altering 'log_file' for each ip
log_file -noappend router_${ip}_status.log
# Your further code here...
}

Expect script: to perform actions after closing ssh

The question is to preserve a variable and to perform actions after closing ssh within expect script inside bash.
This is what I`ve got so far:
echo "Getting package name..."
getPackageName=$(expect -c '
exp_internal 1
log_user 1
global expect_out
# puts "Getting package name..."
spawn ssh -q -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no -o RSAAuthentication=no -l user 10.20.30.40
sleep 1
expect {
"*sword*" {
send "12341234\r"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect {
"*user#*>*" {
# getting name of the latest modified file
send "cd /export/home/user/Releases/build/1.3.32.0 && find * -type f -printf '"'"'%T# %p\\n'"'"' | sort -n | tail -1 | cut -f2- -d\" \"\r"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect {
"BUILD_MAIN*" {
# assigning value to variable
set result_lines [split $expect_out(0,string) \r\n]
set package_filename [lindex $result_lines 0]
puts "package_filename: $package_filename"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect "*#"
send "exit\r"
# here I need to perform some actions on local machine after ssh logout
expect "Connection*"
send "export LATEST_BUILD=$package_filename\r"
send_user "Message sent to user"
')
So, in the bottom block I am trying to set environment variable (LATEST_BUILD) on the local machine after closing ssh, and also to paste there a value of variable (package_filename) which has been defined earlier during ssh session.
The point here is that I see the last "Message sent to user" in the output, but the previous send "export LATEST_BUILD=12345\r" obviously does not work.
#!/bin/bash
getPackageName=$(expect -c '
# A common prompt matcher
set prompt "%|>|#|\\\$ $"
# To suppress any other form of output generated by spawned process
log_user 0
### Spawning ssh here ###
spawn ssh user#xxx.xx.xxx.xxx
expect "password"
send "welcome!2E\r"
expect -re $prompt
# Your further code
send "exit\r"
expect eof
##### The below segment is not needed ######
##### if your intention is to get only the 'package_filename' value #####
# spawn bash
# expect -re $prompt
# send "export LATEST_BUILD=54.030\r"
# expect -re $prompt
# send "echo \$LATEST_BUILD\r"
# expect -re $prompt
# send "exit\r"
# expect eof
#
##### The End ######
# Enabling logging now ...
log_user 1
# Print only the value which you want to return
puts "$package_filename"
')
echo $getPackageName
eof is used to identify the end-of-file event i.e. closure of connection.
Note : The exported variable LATEST_BUILD only be available for the spawned bash session.
Update :
log_user is used to turn off/on the logging generated by Expect at any time.
log_user 0; # Turn off logging
log_user 1; # Turn on logging
I hope that your only intention is to get the package_filename. So, we don't even need to spawn bash shell. Instead, simply print the value at last, thereby making it to be available to the parent bash script.

shell script to ssh remote machines and print the output of the top command

I want to write a shell script to do the following four things:
ssh a remote machine (say hosti)
print the machine name to a file (top_out)
print the first few lines of the output of the 'top' command to the same file as in step2
repeat 1-3 for an other machine
I tried this:
#! /bin/bash
for i in 1 2 3 4 5 6 7 8
do
echo "host$i" >> ~/mysh/top_out
ssh host$i "top -n1 -b | head -n 15>> ~/mysh/top_out"
echo "done"
done
The output file that I got had saved the top output for some machines (say like host5-8), but it was blank for the early machinessay like host1-4. If I tried without the line "echo "host$i" >> ~/mysh/top_out", I can get the top output for all the host1-8.
When you do
ssh host$i "top -n1 -b | head -n 15>> ~/mysh/top_out"
you’re writing the output to ~/mysh/top_out on the remote host, not the local machine. The remote host might not be using the same physical home directory as your local machine. If you have NFS or something sharing your home directory on some machines but not all, then you’d see the symptoms you described.
Try doing
ssh host$i "top -n1 -b | head -n 15" >> ~/mysh/top_out
instead, or to make things slightly cleaner, maybe even
#!/bin/bash
for i in $(seq 1 8); do
(echo "host$i"
ssh host$i "top -n1 -b | head -n 15") >> ~/mysh/top_out
echo "done host$i"
done
you can try an expect script to save the output of each hosts after it connects to it, you can also add more commands to it, p.s. : this assumes you have the same username and password for all hosts :
#/usr/bin/expect -f
#write your hosts on a new line inside a file and save it in your workging directory as:
#host 1
#host 2
#host 3
#user '' for password if it contains special chars
#pass arguments to the script as ./script $username '$password' $hosttxt
set user [lindex $argv 0]
set pass [lindex $argv 1]
#pass path to txt file with line separated hosts
set fhost [lindex $argv 2]
#set this to the path where you need to save the output e.g /home/user/output.txt
set wd "/home/$user/log.txt"
#open hosts file for parsing
set fhosts [open $fhost r]
exec clear
#set loguser 1
proc get_top {filename line user pass} {
spawn ssh -l $user $line
expect {
"$ " {}
"(yes/no)? " {
send "yes\r"
expect -re "assword:|assword: "
send "$pass\r"
}
-re "assword:|assword: " {
send "$pass\r"
}
default {
send_user "Login failed\n"
exit 1
}
}
expect "$ " {}
send "top -n1 -b | head -n 15\r"
expect -re "\r\n(.*)\r(.*)(\\\$ |# )" {
set outcome "$expect_out(1,string)\r"
send "\r"
}
puts $filename "$outcome\n\n-------\n"
}
while {[gets $fhosts line] !=-1} {
set filename [open $wd "a+"]
get_top $filename $line $user $pass
close $filename
}
Check if the hosts for which you are not getting the output is showing following error:
TERM environment variable not set.
If you are getting this error for some of the hosts you can try following command:
ssh user#host "screen -r; top" >> file_where_you_want_to_save_output

Resources