scp in Expect script works for few files but not for many files - remote-access

I wrote a bash code to send files to a sever computer from my remote laptop. I used 'scp' command and wrote it on a bash script to bypass entering a password every time I ran it.
expect <<EOF
spawn scp -P 1111 -o StrictHostKeyChecking=no -r /Users/Desktop/sync_mac user#192.111.111.101:/home/folder
expect "password:"
send "11111\r"
expect eof
EOF
However, the problem is when I ran the bash script on the terminal, it seemed like working well but suddenly failed sending files without any sign of warnings.(Especially for the case of sending a large number of files or a large size of file, it was okay for the case of a small number and a small size)
Thanks for your help

The default timeout for expect is 10 seconds so expect eof would wait for at most 10 seconds which may be not enough for many files as you mentioned.
To fix, you can set timeout -1 before expect eof or just expect -timeout -1 eof.

I had to do this a while back, but for multiple servers, the list of which would change from night to night. I discovered sometimes scp would ask to add the host to your list of known hosts first, or other messages for which I had to code handling. That could be getting buried in your script.
This is an excerpt of what ultimately worked, if that helps:
expect -c "
spawn ssh-copy-id -i /x/home/$USER/.ssh/id_rsa.pub $USER#$HOST
expect {
\"password:\" {
send \"$PASS\n\"
expect {
\"expecting.\" { }
timeout {exit 1}
\"again.\" {exit 1}
}
}
\"yes/no)?\" {
send \"yes\n\"
expect \"password:\" {
send \"$PASS\n\"
expect {
\"expecting.\" { }
timeout {exit 1}
\"again.\" {exit 1}
}
}
}
}

Related

expect ssh reg_ex for remote prompt

I have an shell script which uses expect to launch a ssh session to another server and list a directory. It works fine but my question is what is the best way to handle remote prompts after logging in? For example, here is what I have so far:
# Wait for the prompt on a remote ssh server
-re "\[%|>|\$|#\] $" {
send "ls -1t /home/user/\r"
expect {
"*not found" {
puts "\nDirectory not found\n"
exp_continue
}
timeout { puts "\ntimeout happened\n" }
-re "\[%|>|\$|#\] $" {
puts "Exiting..."
send "exit\r"
return
}
}
}
So, after a successful login the expect script waits for the prompt before sending the ls -1t /home/user/ command. The prompt can be different depending how how the ssh destination is setup. So far I'm checking for
-re "\[%|>|\$|#\] $"
prompts handled so far....
%
>
$
#
I would like to make this as generic as possible so I know the destination ssh server is ready to receive the ls -1t /home/user/ command.
Is there a better way to do this or should I add more cases to the reg_ex in my expect script?
I've also tried -re ". $" but this doesn't work

Why spawn scp does not work correctly?

Below is my script containing spawn scp related script.
spawn scp -r /usr/local/clo/Jenkins.zip root#xu.domain.com:/usr/local/clo
expect "password:"
send "111111\r"
expect "*\r"
expect "\r"
But when I execute it, it output the following log and it can not transfer the file correctly.
**
spawn scp -r /usr/local/clo/Jenkins.zip root#xu.domain.com:/usr/local/clo
root#xu.domain.com's password:
Jenkins.zip 0% 0 0.0KB/s --:-- ETA
**
I was wondering how does the '0%' happen? If I manually execute the scp script from console, it can send the file completely.
I suspect this happens because the output of the spawned process includes \r (carriage return) characters when displaying the progress meter, try to add expect eof at the end of your Expect script.
But to deal with timeout problems, it should be better to detect the progress meter, using exp_continue to continue executing while the progress meter is displayed:
spawn scp -r /usr/local/clo/Jenkins.zip root#xu.domain.com:/usr/local/clo
expect "password:"
send "111111\r"
expect {
"ETA" {exp_continue}
"100%" {}
}
expect eof
You could also add exp_internal 1 at the beginning of the script (it reports internal activity of the expect command).

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.

Execute a shell script on a remote server via Expect

I need to setup a cron job on multiple servers (all Unix based). For this, I have created a script (lets refer to it as script "A") which sets up the cron job correctly when executed manually. Also, I have uploaded this script to all the target servers with the intention of calling and executing it automatically via another script on another server.
Now, this other server contains the script "B" (as mentioned above) which logs into all the target servers and calls script A in order to automatically setup the cron on all servers.
The problem is, script A is never called and cron is never setup. Script B is an Expect script and here's the code,
spawn rsync -avz -e ssh $localDir/autodir $username#$ipaddress:$remoteDir
sleep 10
expect {
"assword:" {
send "$pwd\r"
sleep 5
exp_continue
}
expect {
"#" {
send "cd /home/$username/autodir/config\r"
sleep 5
exp_continue
expect "#"
send "./setCron.sh" # NEED TO EXECUTE THIS AT REMOTE SERVER
sleep 5
exp_continue
}
}
"yes/no" {
send "yes\r"
set timeout -5
exp_continue
}
-re $prompt {
send "\r"
}
timeout {
exit
}
eof {
exit
}
}
Basically, I am looking for a general purpose solution to call a script on a remote server without using ssh or public key authentication because I cannot use both of these methods due to some limitations. Hence I tried using Expect but to no success. I could not find the proper solution even after a lot of rummaging. Help needed here!
Just discovered the solution while tinkering around. Here's the code,
spawn rsync -avz -e ssh $localDir/autodir $username#$ipaddress:$remoteDir
sleep 10
expect {
"assword:" {
send "$pwd\r"
sleep 5
exp_continue
}
timeout {
exit
}
}
spawn ssh $username#$ipaddress
sleep 10
expect {
"assword:" {
send "$pwd\r"
sleep 5
send "sh /home/$username/autodir/config/setCron.sh\r"
sleep 5
exp_continue
}
timeout {
exit
}
}
This logs into all my servers, syncs the file/directories and sets up the cron job. Works like a charm. I had to use ssh in the end though.

How to automate telnet session using Expect?

I'm trying to write an expect script to automate telnet. This is what I have so far.
#!/usr/bin/expect
# Test expect script to telnet.
spawn telnet 10.62.136.252
expect "foobox login:"
send "foo1\r"
expect "Password:"
send "foo2\r"
send "echo HELLO WORLD\r"
# end of expect script.
Basically, what I want to do is telnet to the following IP address and then echo HELLO WORLD. However, it seems that the script fails after attempting to telnet...I'm not sure if it's able to accept login and password input, but it is not echoing HELLO WORLD. Instead, I just get this output:
cheungj#sfgpws30:~/justin> ./hpuxrama
spawn telnet 10.62.136.252
Trying 10.62.136.252...
Connected to 10.62.136.252.
Escape character is '^]'.
Welcome to openSUSE 11.1 - Kernel 2.6.27.7-9-pae (7).
foobox login: foo1
Password: foo2~/justin>
It's hard to tell, but from the output you're pasting it looks like:
Your script isn't waiting for login to complete before sending the next command.
Your script is exiting and closing the process before you can see any output.
There are no guarantees in life, but I'd try this as a first step:
#!/usr/bin/expect -f
spawn telnet 10.62.136.252
expect "foobox login:"
send "foo1\r"
expect "Password:"
send "foo2\r"
# Wait for a prompt. Adjust as needed to match the expected prompt.
expect "justin>"
send "echo HELLO WORLD\r"
# Wait 5 seconds before exiting script and closing all processes.
sleep 5
Alternatives
If you can't get your script to work by manually programming it, try the autoexpect script that comes with Expect. You can perform your commands manually, and autoexpect will generate an Expect typescript based on those commands, which you can then edit as needed.
It's a good way to find out what Expect actually sees, especially in cases where the problem is hard to pin down. It's saves me a lot of debugging time over the years, and is definitely worth a try if the solution above doesn't work for you.
You're sending the echo command without first expecting the prompt. Try:
# after sending the password
expect -re "> ?$"
send "echo HELLO WORLD\r"
expect eof
Have you seen this StackOverflow Question?
He seems to have got things working by using curly braces.
Here is a simplified version
#!/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 "'^]'."

Resources