how to acquire the return value of a command in ssh accessed by expect? - linux

After i use expect access ssh, and run particular command in it. How could i get the actually return value of that command and set it to the return value of expect or an env variable(better)?
I am not so familiar with expect, so a few lines to show how the capture thing works would help a lot. Many thanks.

You do it the same way you would sitting at a terminal: echo $? (I assume your remote shell is sh/ksh/bash/...)
#!/usr/bin/expect -f
set host remote_host
set user remote_username
set prompt {\$ $}
set cmd {grep not-found /etc/passwd}
log_user 0
spawn ssh -l $user $host
expect -re $prompt
send "$cmd\r"
expect -re $prompt
send -- "echo \$?\r"
expect -re "\r\n(\\d+)\r\n.*$prompt"
set rc $expect_out(1,string)
send -- "exit\r"
expect eof
puts "return code from '$cmd' on $host = $rc"

Related

how to use expect in linux? [duplicate]

I'm trying to use expect in a Bash script to provide the SSH password. Providing the password works, but I don't end up in the SSH session as I should. It goes back strait to Bash.
My script:
#!/bin/bash
read -s PWD
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com'
expect "password"
send "$PWD\n"
EOD
echo "you're out"
The output of my script:
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
usr#$myhost.example.com's password: you're out
I would like to have my SSH session and, only when I exit it, to go back to my Bash script.
The reason why I am using Bash before expect is because I have to use a menu. I can choose which unit/device to connect to.
To those who want to reply that I should use SSH keys, please abstain.
Mixing Bash and Expect is not a good way to achieve the desired effect. I'd try to use only Expect:
#!/usr/bin/expect
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
# Use the correct prompt
set prompt ":|#|\\\$"
interact -o -nobuffer -re $prompt return
send "my_password\r"
interact -o -nobuffer -re $prompt return
send "my_command1\r"
interact -o -nobuffer -re $prompt return
send "my_command2\r"
interact
Sample solution for bash could be:
#!/bin/bash
/usr/bin/expect -c 'expect "\n" { eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com; interact }'
This will wait for Enter and then return to (for a moment) the interactive session.
The easiest way is to use sshpass. This is available in Ubuntu/Debian repositories and you don't have to deal with integrating expect with Bash.
An example:
sshpass -p<password> ssh <arguments>
sshpass -ptest1324 ssh user#192.168.1.200 ls -l /tmp
The above command can be easily integrated with a Bash script.
Note: Please read the Security Considerations section in man sshpass for a full understanding of the security implications.
Add the 'interact' Expect command just before your EOD:
#!/bin/bash
read -s PWD
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
expect "password"
send -- "$PWD\r"
interact
EOD
echo "you're out"
This should let you interact with the remote machine until you log out. Then you'll be back in Bash.
After looking for an answer for the question for months, I finally find a really best solution: writing a simple script.
#!/usr/bin/expect
set timeout 20
set cmd [lrange $argv 1 end]
set password [lindex $argv 0]
eval spawn $cmd
expect "assword:" # matches both 'Password' and 'password'
send -- "$password\r"; # -- for passwords starting with -, see https://stackoverflow.com/a/21280372/4575793
interact
Put it to /usr/bin/exp, then you can use:
exp <password> ssh <anything>
exp <password> scp <anysrc> <anydst>
Done!
A simple Expect script:
File Remotelogin.exp
#!/usr/bin/expect
set user [lindex $argv 1]
set ip [lindex $argv 0]
set password [lindex $argv 2]
spawn ssh $user#$ip
expect "password"
send "$password\r"
interact
Example:
./Remotelogin.exp <ip> <user name> <password>
Also make sure to use
send -- "$PWD\r"
instead, as passwords starting with a dash (-) will fail otherwise.
The above won't interpret a string starting with a dash as an option to the send command.
Use the helper tool fd0ssh (from hxtools, source for ubuntu, source for openSUSE, not pmt). It works without having to expect a particular prompt from the ssh program.
It is also "much safer than passing the password on the command line as sshpass does" ( - comment by Charles Duffy).
Another way that I found useful to use a small Expect script from a Bash script is as follows.
...
Bash script start
Bash commands
...
expect - <<EOF
spawn your-command-here
expect "some-pattern"
send "some-command"
...
...
EOF
...
More Bash commands
...
This works because ...If the string "-" is supplied as a filename, standard input is read instead...
sshpass is broken if you try to use it inside a Sublime Text build target, inside a Makefile. Instead of sshpass, you can use passh
With sshpass you would do:
sshpass -p pa$$word ssh user#host
With passh you would do:
passh -p pa$$word ssh user#host
Note: Do not forget to use -o StrictHostKeyChecking=no. Otherwise, the connection will hang on the first time you use it. For example:
passh -p pa$$word ssh -o StrictHostKeyChecking=no user#host
References:
Send command for password doesn't work using Expect script in SSH connection
How can I disable strict host key checking in ssh?
How to disable SSH host key checking
scp without known_hosts check
pam_mount and sshfs with password authentication

How do I ssh into a machine, wait for a prompt, and then send a bunch of commands to it?

How do I do the following?
SSH to a machine, probably using expect since this is a script and I can't type the password for it
Wait for a prompt from the machine. The prompt is ->
Then send a bunch of commands from the original host to the machine I ssh'd to. Note that the output from these commands can contain the characters ->
Exit
Here are the contents of some-commands.txt:
first command
second command
third command
Here are the contents of the expect script:
#!/usr/bin/expect
set f [open "some-commands.txt"]
set cmds [split [read $f] "\n"]
close $f
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no root#machine
interact -o -nobuffer -re "Password:" return
send "password\r"
# look for the prompt
set prompt "\n-> "
foreach cmd $cmds {
send "$cmd\r";
# The following works, except for the commands
# whose output include ->
interact -o -nobuffer -re "-> " return
}
The problem is that the interact command captures the -> from the command output instead of the prompt, which hasn't yet arrived at that point.
I'm used to accomplish the same thing by doing something like:
ssh -t -t -C user#host 'bash -s' < my_shell_script.sh param1 paramX
Where my_shell_script.sh is an simple shell script.
The trick here is use multiple -t to force pseudo-terminal over ssh and the -s option to bash witch makes it reads the commands from the standard input.

How to return from shell 'read' command passed in expect script?

I am really new to using expect, and a bit confused regarding passing commands to an expect script, so please bear with me... I have searched numerous forums, but cannot seem to find an example of an expect script that uses the read command to get user input.
In my Korn shell script, I call an expect script (expectssh.exp) to ssh login to another host and get user input regarding that host's network configuration (network interface card number and subnet mask information). I pass four arguments to the expect script: the remote host ip address, the username, the password, and the list of commands to run. My expect script is below:
#!/usr/bin/expect
# Usage: expectssh <host> <ssh user> <ssh password> <script>
set timeout 60
set prompt "(%|#|\\$) $"
set commands [lindex $argv 3];
spawn ssh [lindex $argv 1]#[lindex $argv 0]
expect {
"*assword:" {
send -- "[lindex $argv 2]\r"
expect -re "$prompt"
send -- "$commands\r"
}
"you sure you want to continue connecting" {
send -- "yes\r"
expect "*assword:"
send -- "[lindex $argv 2]\r"
expect -re "$prompt"
send -- "$commands\r"
}
timeout {
exit }
}
The script runs well, except that when it gets to the 'read' command, the script does not continue or exit after the user presses enter. It just hangs.
The commands I pass to the expect script and its call are as follows:
SCRIPT='hostname > response.txt;netstat -rn;read net_card?"What is the network interface card number? " >> response.txt; read net_mask?"What is the subnet mask? " >> response.txt'
/usr/bin/expect ./expectssh.exp $hostip $usr $pswd "$SCRIPT"
Any suggestions on how I can pass the read command through my expect script without it hanging?
On a side note because I know it will come up - I am not allowed to do key-based automatic SSH login. I have to prompt for a username and password, which is done from the Korn shell script that calls this expect script.
Thanks for any suggestions and help you can provide!
For anyone interested, I was able to get the read command to work for user input by doing a few things:
(1) Putting it within an -re $prompt block instead of appending send -- "$commands\r" after the password entry.
(2) Hard coding the commands into the script rather than passing them in.
(3) Following the command with an interact statement so that the next send command isn't entered before the user responds.
My expect block now looks like this:
expect {
-re "(.*)assword:" {
send -s "$pswd\r"
exp_continue
}
"denied, please try again" {
send_user "Invalid password or account.\n"
exit 5
}
"incorrect" {
send_user "Invalid password or account.\n"
exit 5
}
"you sure you want to continue connecting" {
send -s "yes\r"
exp_continue
}
-re $prompt {
set timeout -1
send -- "hostname > partnerinit\r"
expect -exact "hostname > partnerinit\r"
send -s "netstat -rn\r"
expect -re "$prompt"
send -- "read n_card?'Enter the network interface card number for this server (i.e. eth0): '\r"
interact "\r" return
send -- "\r"
send -- "echo \$n_card >> partnerinit\r"
send -- "msk=\$(cat /etc/sysconfig/network-scripts/ifcfg-\$n_card | grep NETMASK)\r"
send -- "msk=\$(echo \${msk#NETMASK=})\r"
send -- "echo \$msk >> partnerinit\r"
send -- "cat partnerinit\r"
set retval 0
}
timeout {
send_user "Connection to host $host timed out.\n"
exit 10
}
eof {
send_user "Connection to host $host failed.\n"
exit 1
}
}
I also updated the script to automatically determine the subnet mask based on the network interface card number entered by the user. It was brought to my attention that finding the network interface card number would be very difficult to automate in the case of a box that has multiple interface cards. Better to start small and have the user enter it and fine-tune/automate it later once the overall script is working.
Now I'm working on modifying this to scp my partnerinit file back to my local host and to return meaningful exit statuses from each of the expect conditions.

Running shell command after expect login

Iam trying to exceute a command after logging in to a linux RHEL box using expect and interact.
Below is script
#!/usr/bin/expect
set timeout 100
set temp [lindex $argv 0]
spawn ssh userid#10.20.30.40
expect "Password:"
send "password\n";
interact
expect "*3.2*"
send "./p.sh\n";
Its successfully logging in to the box but after that its not excecuting the command.
This is the actual output of the commnad after login , which iam trying to exceute.
Using keyboard-interactive authentication.
Password:
Last login: Sun Mar 22 11:04:01 2015 from com
-sh-3.2$ pbrun pbapp wasapp=ksh
Please note home directories are intended only for user/application profiles.
$
These are the errors i received
-sh-3.2$ exit
logout
Connection to 10.20.30.40 closed.
expect: spawn id exp7 not open
while executing
"expect "*$""
(file "./testWas.sh" line 8)
when i try
expect "*$"
exec "pwd"
-sh-3.2$ exit
logout
Connection to 10.20.30.40 closed.
couldn't execute " pwd ": no such file or directory
while executing
"exec { pwd }"
(file "./testWas.sh" line 8)
Edit:
Thanks to red #glenn jackman
iam able to excute pbrun commands after login..
But after excecuting the pbrun command script is exiting
#!/usr/bin/expect
set timeout 100
set host [lindex $argv 0]
set username [lindex $argv 1]
set password [lindex $argv 2]
set command [lindex $argv 3]
spawn ssh $username#$host expect "Password:"
send "$password\n";
expect -re {\$ $} ; # put here string from your server prompt
send "./p.sh\n";
expect -re {\$ $} ;
send "pwd\n";
This is the content of p.sh
Only first line of the script is executing..
-sh-3.2$ cat p.sh
pbrun pbapp wsapp=ksh
pwd
clear
-sh-3.2$
There is a similar unanswered question
How to run "pbrun pbapp wasapp=ksh" command using SSH java client?
interact tells expect that you are going into manual mode, where you, the human, is in control of the spawned command. I see you then typed exit which ended the ssh session. Since the spawned command ended, the interact command ended and control returned to the script. The next command dies because the spawned command is not running.
Simply put, remove interact:
#!/usr/bin/expect
set timeout 100
set temp [lindex $argv 0]
spawn ssh userid#10.20.30.40
expect "Password:"
send "password\r" # a carriage return more exactly represents
# "hitting enter"
expect -re {\$ $} # this regular expression matches the end of the prompt
send "./p.sh\r"
if { you want to interact manually with the ssh session } {
interact
} else {
expect -re {\$ $} # if p.sh exits the ssh session, remove this line
send "exit\r" # and this one too.
expect eof
}

Automating telnet session using Bash scripts

I am working on automating some telnet related tasks, using Bash scripts.
Once automated, there will be no interaction of the user with telnet (that is, the script will be totally automated).
The scripts looks something like this:
# execute some commands on the local system
# access a remote system with an IP address: 10.1.1.1 (for example)
telnet 10.1.1.1
# execute some commands on the remote system
# log all the activity (in a file) on the local system
# exit telnet
# continue with executing the rest of the script
There are two problems I am facing here:
How to execute the commands on the remote system from the script (without human interaction)?
From my experience with some test code, I was able to deduce that when telnet 10.1.1.1 is executed, telnet goes into an interactive session and the subsequent lines of code in the script are executed on the local system. How can I run the lines of code on the remote system rather than on the local one?
I am unable to get a log file for the activity in the telnet session on the local system. The stdout redirect I used makes a copy on the remote system (I do not want to perform a copy operation to copy the log to the local system). How can I achieve this functionality?
While I'd suggest using expect, too, for non-interactive use the normal shell commands might suffice. telnet accepts its command on stdin, so you just need to pipe or write the commands into it through heredoc:
telnet 10.1.1.1 <<EOF
remotecommand 1
remotecommand 2
EOF
(Edit: Judging from the comments, the remote command needs some time to process the inputs or the early SIGHUP is not taken gracefully by telnet. In these cases, you might try a short sleep on the input:)
{ echo "remotecommand 1"; echo "remotecommand 2"; sleep 1; } | telnet 10.1.1.1
In any case, if it's getting interactive or anything, use expect.
Write an expect script.
Here is an example:
#!/usr/bin/expect
#If it all goes pear shaped the script will timeout after 20 seconds.
set timeout 20
#First argument is assigned to the variable name
set name [lindex $argv 0]
#Second argument is assigned to the variable user
set user [lindex $argv 1]
#Third argument is assigned to the variable password
set password [lindex $argv 2]
#This spawns the telnet program and connects it to the variable name
spawn telnet $name
#The script expects login
expect "login:"
#The script sends the user variable
send "$user "
#The script expects Password
expect "Password:"
#The script sends the password variable
send "$password "
#This hands control of the keyboard over to you (Nice expect feature!)
interact
To run:
./myscript.expect name user password
Telnet is often used when you learn the HTTP protocol. I used to use that script as a part of my web scraper:
echo "open www.example.com 80"
sleep 2
echo "GET /index.html HTTP/1.1"
echo "Host: www.example.com"
echo
echo
sleep 2
Let's say the name of the script is get-page.sh, then this will give you an HTML document:
get-page.sh | telnet
I hope this will be helpful to someone ;)
This worked for me..
I was trying to automate multiple telnet logins which require a username and password. The telnet session needs to run in the background indefinitely since I am saving logs from different servers to my machine.
telnet.sh automates telnet login using the 'expect' command. More info can be found here: http://osix.net/modules/article/?id=30
telnet.sh
#!/usr/bin/expect
set timeout 20
set hostName [lindex $argv 0]
set userName [lindex $argv 1]
set password [lindex $argv 2]
spawn telnet $hostName
expect "User Access Verification"
expect "Username:"
send "$userName\r"
expect "Password:"
send "$password\r";
interact
sample_script.sh is used to create a background process for each of the telnet sessions by running telnet.sh. More information can be found in the comments section of the code.
sample_script.sh
#!/bin/bash
#start screen in detached mode with session-name 'default_session'
screen -dmS default_session -t screen_name
#save the generated logs in a log file 'abc.log'
screen -S default_session -p screen_name -X stuff "script -f /tmp/abc.log $(printf \\r)"
#start the telnet session and generate logs
screen -S default_session -p screen_name -X stuff "expect telnet.sh hostname username password $(printf \\r)"
Make sure there is no screen running in the backgroud by using the
command 'screen -ls'.
Read
http://www.gnu.org/software/screen/manual/screen.html#Stuff to read
more about screen and its options.
'-p' option in sample_script.sh
preselects and reattaches to a specific window to send a command via
the ‘-X’ option otherwise you get a 'No screen session found' error.
You can use expect scripts instaed of bash.
Below example show how to telnex into an embedded board having no password
#!/usr/bin/expect
set ip "<ip>"
spawn "/bin/bash"
send "telnet $ip\r"
expect "'^]'."
send "\r"
expect "#"
sleep 2
send "ls\r"
expect "#"
sleep 2
send -- "^]\r"
expect "telnet>"
send "quit\r"
expect eof
The answer by #thiton was helpful but I wanted to avoid the sleep command. Also telnet didn't exit the interactive mode, so my script got stuck.
I solved that by sending telnet command with curl (which seems to wait for the response) and by explicitly telling telnet to quit like this:
curl telnet://10.1.1.1:23 <<EOF
remotecommand 1
remotecommand 2
quit
EOF
Following is working for me...
put all of your IPs you want to telnet in IP_sheet.txt
while true
read a
do
{
sleep 3
echo df -kh
sleep 3
echo exit
} | telnet $a
done<IP_sheet.txt
#!/bin/bash
ping_count="4"
avg_max_limit="1500"
router="sagemcom-fast-2804-v2"
adress="192.168.1.1"
user="admin"
pass="admin"
VAR=$(
expect -c "
set timeout 3
spawn telnet "$adress"
expect \"Login:\"
send \"$user\n\"
expect \"Password:\"
send \"$pass\n\"
expect \"commands.\"
send \"ping ya.ru -c $ping_count\n\"
set timeout 9
expect \"transmitted\"
send \"exit\"
")
count_ping=$(echo "$VAR" | grep packets | cut -c 1)
avg_ms=$(echo "$VAR" | grep round-trip | cut -d '/' -f 4 | cut -d '.' -f 1)
echo "1_____ping___$count_ping|||____$avg_ms"
echo "$VAR"
Use ssh for that purpose. Generate keys without using a password and place it to .authorized_keys at the remote machine. Create the script to be run remotely, copy it to the other machine and then just run it remotely using ssh.
I used this approach many times with a big success. Also note that it is much more secure than telnet.
Here is how to use telnet in bash shell/expect
#!/usr/bin/expect
# just do a chmod 755 one the script
# ./YOUR_SCRIPT_NAME.sh $YOUHOST $PORT
# if you get "Escape character is '^]'" as the output it means got connected otherwise it has failed
set ip [lindex $argv 0]
set port [lindex $argv 1]
set timeout 5
spawn telnet $ip $port
expect "'^]'."
Script for obtain version of CISCO-servers:
#!/bin/sh
servers='
192.168.34.1
192.168.34.3
192.168.34.2
192.168.34.3
'
user='cisco_login'
pass='cisco_password'
show_version() {
host=$1
expect << EOF
set timeout 20
set host $host
set user $user
set pass $pass
spawn telnet $host
expect "Username:"
send "$user\r"
expect "Password:"
send "$pass\r"
expect -re ".*#"
send "show version\r"
expect -re ".*-More-.*"
send " "
expect -re ".*#"
send "exit\r"
EOF
}
for ip in $servers; do
echo '---------------------------------------------'
echo "$ip"
show_version $ip | grep -A3 'SW Version'
done
Here is a solution that will work with a list of extenders. This only requires bash - some of the answers above require expect and you may not be able to count on expect being installed.
#!/bin/bash
declare -a Extenders=("192.168.1.48" "192.168.1.50" "192.168.1.51")
# "192.168.1.52" "192.168.1.56" "192.168.1.58" "192.168.1.59" "192.168.1.143")
sleep 5
# Iterate the string array using for loop
for val in ${Extenders[#]}; do
{ sleep 0.2; echo "root"; sleep 0.2; echo "ls"; sleep 0.2; } | telnet $val
done
Play with tcpdump or wireshark and see what commands are sent to the server itself
Try this
printf (printf "$username\r\n$password\r\nwhoami\r\nexit\r\n") | ncat $target 23
Some servers require a delay with the password as it does not hold lines on the stack
printf (printf "$username\r\n";sleep 1;printf "$password\r\nwhoami\r\nexit\r\n") | ncat $target 23**

Resources