Remote SSH and command execution using BASH and Expect - bash

I'm trying to achieve the following with a bash script:
try for SSH connection, if fails, error out if SSH connection is
once confirmed, execute the 'top' command and save results to file
scp file back from remote server
I know the invidiual commands, which would be:
1) to check the ssh connection:
ssh -q user#ip exit
echo $?
This should return '0' on success and '255' on error.
2) to execute top and save to file would be:
top -n 1 -b > /tmp/top.out
3) scp back file from remote host
expect -c "
set timeout 1
spawn scp user#host:/tmp/top.out root#anotherhost:/.
expect yes/no { send yes\r ; exp_continue }
expect password: { send password\r }
expect 100%
sleep 1
exit
"
Now putting this altogether is my problem, to be more specific:
I can't seem to be able to get the returned '0' and '255' values when using expect to test the SCP connection.
I can't seem to be able to execute the top command using expect again, i.e. this doesn't work:
expect -c "
set timeout 1
spawn ssh user#host top -n 3 -b > /tmp/top.out
expect password: { send password\r }
sleep 1
exit
"
and therefore the 3rd bit won't work either.
Any help is appreciated. Please bear in mind that my script is a .sh script with the #!/bin/bash declaration -- I cannot use #!/usr/bin/expect for various reasons.

Now that's ugly stuff. There are many pitfalls, probably you are not quite sure at what places you execute with a shell, or with plain argv array. Also the expect stuff is not the way that is supposed to be done, that's only brittle.
(AFAIK, expect is mainly meant to be used to communicate with modems (AT command set) and the like).
Use SSH keys for automated ssh and scp instead of passwords. Once you've done that (or even before, but then you have to enter passwords manually), launch this in your shell:
$ ssh user#server "top -n 1 -b" > /tmp/top.out
and the file will be on your local machine. (Because redirection was done locally, not yet remotely). No need to scp.
That's all there is to it.

Check this one:
https://stackoverflow.com/a/23632210/524743
expect <<'END'
log_user 0
spawn sh -c {echo hello; exit 42}
expect eof
puts $expect_out(buffer)
lassign [wait] pid spawnid os_error_flag value
if {$os_error_flag == 0} {
puts "exit status: $value"
} else {
puts "errno: $value"
}
END
hello
exit status: 42
From the expect man page
wait [args]
delays until a spawned process (or the current process if none is named) terminates.
wait normally returns a list of four integers. The first integer is the pid of the process that was waited upon. The second
integer is the corresponding spawn id. The third integer is -1 if an
operating system error occurred, or 0 otherwise. If the third integer
was 0, the fourth integer is the status returned by the spawned
process. If the third integer was -1, the fourth integer is the value
of errno set by the operating system. The global variable errorCode is
also set.

Related

Need modification in an ssh script that's written in expect

Am working on a script to ssh into list of servers using expect tool. Getting below error while running it
./script
#!/usr/local/bin/expect -f
while /usr/bin/read hostname
do
spawn ssh user#$hostname
expect "user#$hostname's password"
send "resuidt\n"
expect "user#$hostname"
interact
done < srvlist
Below is my error:
missing operand at _#_
in expression "_#_/usr/bin/read"
(parsing expression "/usr/bin/read")
invoked from within
"while /usr/bin/read hostname"
(file "./script" line 3)
Need help to fix this error.
You are writing an Expect program, which is basically a Tcl program. Your while loop is not Tcl syntax, but looks like a (Posix/Ksh/Bash/Zsh)-shell script.
You have to make up your mind: Write everything in Tcl, or split your application into two files: One (in shell script) as "main program", and a separate expect script, which will be called by the shell script.
As user1934428 indicates you are using bash-type while loop syntax.
Below is one example of how to make an expect script perform the actions you want.
#!/usr/local/bin/expect -f
set file hostname
set user myusername
set passwd mypassword
set f [open $file]
foreach target [split [read $f] "\n"] {
spawn ssh $user#$target
expect {
timeout {send_user "Expect Timeout\n" ; exit}
"password:"
}
send "$passwd\r"
expect {
timeout {send_user "Expect Timeout\n" ; exit}
"$user#$target"
}
interact
}
close $f
I included timeouts in the expect sections because I've found if you do not add these safety mechanisms the expect script can proceed even without the proper responses.
if you want to use shell variables directly into the expect script then you have to pass those variables as $env(shell_variable_name) inside the expect script
example:spawn ssh $env(myusername)#$env(hostname)

expect: launching scp after sftp

I could really use some help. I'm still pretty new with expect. I need to launch a scp command directly after I run sftp.
I got the first portion of this script working, my main concern is the bottom portion. I really need to launch a command after this command completes. I'd rather be able to spawn another command than, hack something up like piping this with a sleep command and running it after 10 s or something weird.
Any suggestions are greatly appreciated!
spawn sftp user#host
expect "password: "
send "123\r"
expect "$ "
sleep 2
send "cd mydir\r"
expect "$ "
sleep 2
send "get somefile\r"
expect "$ "
sleep 2
send "bye\r"
expect "$ "
sleep 2
spawn scp somefile user2#host2:/home/user2/
sleep 2
So i figured out I can actually get this to launch the subprocess if I use "exec" instead of spawn.. in other words:
exec scp somefile user2#host2:/home/user2/
the only problem? It prompts me for a password! This shouldn't happen, I already have the ssh-keys installed on both systems. (In other words, if I run the scp command from the host I'm running this expect script on, it will run without prompting me for a password). The system I'm trying to scp to, must be recognizing this newly spawned process as a new host, because its not picking up my ssh-key. Any ideas?
BTW, I apologize I haven't actually posted a "working" script, I can't really do that without comprimising the security of this server. I hope that doesn't detract from anyones ability to assist me.
I think the problem lies with me not terminating the initially spawned process. I don't understand expect enough to do it properly. If I try "close" or "eof", it simply kills the entire script, which I don't want to do just yet (because I still need to scp the file to the second host).
Ensure that your SSH private key is loaded into an agent, and that the environment variables pointing to that agent are active in the session where you're calling scp.
[[ $SSH_AUTH_SOCK ]] || { # if no agent already running...
eval "$(ssh-agent -s)" # ...then start one...
ssh-add /path/to/your/ssh/key # ...load your key...
started_ssh_agent=1 # and flag that we started it ourselves
}
# ...put your script here...
[[ $started_ssh_agent ]] && { # if we started the agent ourselves...
eval "$(ssh-agent -s -k)" # ...then clean up nicely when done.
}
As an aside, I'd strongly suggest replacing the code given in the question with something like the following:
lftp -u user,123 -e 'get /mydir/somefile -o localfile' sftp://host </dev/null
lftp scp://user2#host2 -e 'put localfile -o /home/user2/somefile' </dev/null
Each connection handled in one line, and no silliness messing around with expect.

How to use error codes inside bash script

Apologies if this has been asked before, I was unable to find an answer...
I have created a bash script for OS X to mount an AFP share. The script works successfully and allows a user to type their username and password into a graphical popup using cocoa dialog
My issue is that if the username and password are entered incorrectly, I want to be able to display a new pop up window explaining this.
I was trying to do this based on the exit status of the script but the script returns 0 regardless of whether or not the mount was successful
The script is as follows;
cd=/etc/cocoaDialog.app/Contents/MacOS/cocoaDialog
rvUsername=($($cd standard-inputbox --title "Network Mount" --informative-text "Please enter your username"))
username=${rvUsername[1]}
rvPassword=($($cd secure-standard-inputbox --title "Network Mount" --informative-text "Please enter your password"))
password=${rvPassword[1]}
mkdir "/Volumes/Test"
expect <<- DONE
set timeout -1
spawn /sbin/mount_afp -i "afp://servername/Software" /Volumes/Test
expect "*?ser:*"
send "$username\n"
expect "*?assword:*"
send "$password\r"
expect EOF
DONE
If I run this via Terminal or Textmate, I receive the following (as an example)
spawn /sbin/mount_afp -i afp://server/Software /Volumes/Test
User: username
Password:
mount_afp: the volume is already mounted
Program exited with code #0 after 7.32 seconds.
So mount_afp is giving me a message, which I would then like to use in my script...but the exit code is 0 and I don't know how else to get hold of that message to use
Any ideas? (Hope that makes sense!)
To get the exit code of the spawned command, use something like this:
# wait for the command to end : wait for the prompt $ followed by space
expect "\\$ "
send "echo \$?\r"
expect -re "(\\d+)" {
set result $expect_out(1,string)
}
send_user "command exit with result code : $result"
This will take the content of the variable $? (which is the exit code of the prevously ended command) and save it to $result.
Thanks for the responses all, helped point me in the right direction
I ended up taking this approach and setting the expect command as a variable
output=$(su -l $loggedInUser -c expect <<- DONE
set timeout -1
spawn /sbin/mount_afp -i "afp://$serverName" /Volumes/mount
expect "*?ser:*"
send "$username\n"
expect "*?assword:*"
send "$password\r"
expect EOF
DONE)

Expect Script Terminating on its own

I have written the below expect script but it's not working as expected. I want the script to terminate automatically when all the commands are executed. However , the script either never terminates ( if set timeout -1 is used ) or terminates within seconds even before my commands are executed. Can someone please help ?
Here's the script :
#!/usr/local/bin/expect
spawn su vserve
set password vserve
set PWD whoami
set cmdstr(0) "bash /apps/vpn/vserve/vserve_profile"
set cmdstr(1) "bash /apps/vpn/asap/scripts/change_loopback.sh"
set timeout -1
expect "*Password:*" {
sleep 1
send "$password\r"
send "$PWD\r"
sleep 1
for {set i 0} {$i<[array size cmdstr]} {incr i} {
send "$cmdstr($i)\r"
}
send \"exit\r\"
expect eof
}
Usually in an interactive shell, you have to expect the specific shell prompt before you send next command. That's the way we make sure the previous command has really finished.

How to get telnet execution result code from shell script?

Using the following shell code for remote telnet request:
{
sleep 5
echo admin
sleep 3
echo pass
sleep 3
echo ls
sleep 5
echo exit
} | telnet 172.16.1.1
I want to check if telnet connection was successful or not. Trying to use $?:
echo $?
But it always returns "1", even if telnet connection was OK.
Telnet is exceptionally difficult to script in this way, there is a high degree of asynchronicity with the time it takes to establish a connection and for your intended actions to complete. expect was created for exactly this kind of purpose. You launch a program, like telnet, then declare a series of expectations - eg, when 'username: ' is emitted from the program, and an action to trigger (eg: typing in the username).
There are also libraries or wrappers for expect in many languages:
python expect
ruby expect
perl expect
Here is an example that drives telnet to make an HTTP HEAD request:
set timeout 20
spawn telnet localhost 80
expect "Connected to "
send "HEAD / HTTP/1.0\r\n\r\n"
expect "HTTP 200 OK"
Given all of this, I feel I must point out that telnet is considered insecure. Ssh is a much better choice and supports better choices for authentication (eg: public/private key auth), restrictions for commands that can be run (via .ssh/authorized_keys). With ssh, and ssh-keys set up, your script reduces to a single shell command:
ssh user#hostname ls
ssh has great support for safe, secure remote command execution.
If I'm remembering correctly, this expect script does what you're doing above.
#!/usr/bin/expect
spawn telnet 172.16.1.1
expect username:
send admin
expect password:
send pass
expect "\$ "
send ls
expect "\$ "
send exit
Here's a useful link for getting started: http://oreilly.com/catalog/expect/chapter/ch03.html

Resources