Linux Shell script (bash) to change IP address of remote machine using SSH - linux

I'd like to have a shell script to change the IP address of a remote machine using SSH.
The problem I am having is that both SSH and remote commands require passwords. I am using 'expect' to handle the password input and this works works well in isolation for each step, but not combined.
i.e. I have a separate script on the remote machine to change the IP address. This works when running manually on the remote machine.
#!/usr/bin/expect -f
set ipaddr [lindex $argv 0]
set password "mypassword"
set timeout 2
if {[llength $argv] == 0} {
send_user "Usage: ./set_ip.sh ipaddress/24\n"
exit 1
}
spawn sudo nmcli dev modify eth0 ipv4.addresses $ipaddr
expect {
password { send "$password\r" ; exp_continue }
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof exit
}
close
And I have a script to perform the SSH, then call the above remote script
#!/usr/bin/expect -f
set target [lindex $argv 0]
set newipaddr [lindex $argv 1]
set password "mysshpassword"
set timeout 2
spawn ssh $target
expect {
password: {send "$password\r"}
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof exit
}
send "~/set_ip.sh $newipaddr/r"
send "exit\r"
close
When the remote script gets called, it seems to run okay but the password prompt following the nmcli command is never received.
If someone could advise where I am going wrong I'd be grateful.
Also, this is the first time I've used bash, so feel free to express critique.
Thanks

Related

Use of expect to run scripts on remote machine

I am working on a project that requires some assistance.
I have automated most of the information required for the completion of this project but the only thing that is lagging is the running of local shell scripts on the remote machine.
As we are aware that no Linux command is recognized by the script that uses the 'expect' library.
Herein we have two use cases that I have tried:
1) Running the desired list of commands on the remote server using only one expect script which has both the script execution as well as pushing of output using scp to the local machine, here is a snippet of this code:
`chmod 777 localscript.sh
cat > script1.sh <<- "ALL"`
`#!/usr/bin/expect
set password [lindex $argv 0];
set ipaddress [lindex $argv 1];
set timevalue [lindex $argv 2];
set timeout $timevalue
spawn /usr/bin/ssh username#$ipaddress /bin/bash < ./localscript.sh
expect "assword:"
send "$password\r"
set timeout $timevalue
spawn /usr/bin/scp username#$2:"/path/from/source/*" /path/to/destination/folder/
expect "assword:"
send "$password\r"
interact
ALL
chmod 777 script1.sh
./script1.sh $password $2 $timevalue`
2) Running the desired list of commands on the remote server in a separate expect script and using scp to get files in a different script:
`cat > script1.sh <<- "ALL" `
`#!/usr/bin/expect
set password [lindex $argv 0];
set ipaddress [lindex $argv 1];
set timevalue [lindex $argv 2];
set timeout $timevalue
spawn /usr/bin/ssh username#$ipaddress /bin/bash < ./localscript.sh
expect "assword:"
send "$password\r"
interact
ALL
cat > script2.sh <<- "ALL2"`
`#!/usr/bin/expect
set password [lindex $argv 0];
set ipaddress [lindex $argv 1];
set timevalue [lindex $argv 2];
set timeout $timevalue
spawn /usr/bin/scp username#ipaddress:"/path/from/source/*" /path/to/destination/folder/
expect "assword:"
send "$password\r"
interact
ALL2
chmod 777 localscript.sh script1.sh script2.sh
./script1.sh $password $2 $timevalue
sleep 5
./script2.sh $password $2 $timevalue`
I believe the above codes should both be valid in their own respect however, the output for the same seem to be quite unexpected:
1) Both the commands ssh and scp are being executed almost simultaneously after password is entered hence, it is not giving localscript enough time to do its job, here's the output I see:
spawn /usr/bin/ssh username#1.2.3.4 /bin/bash < ./localscript.sh
Warning private system unauthorized users will be prosecuted.
username#1.2.3.4's password: spawn /usr/bin/scp
username#1.2.3.4:"/home/some/file/*" /another/file/
Warning private system unauthorized users will be prosecuted.
username#1.2.3.4's password:
scp: /home/some/file/*: No such file or directory
Please note: This functionality is working fine without the involvement of expect
2) Here we are executing ssh and scp separately, however, it seems like it is unable to recognize that the file localscript.sh exists:
spawn /usr/bin/ssh username#1.2.3.4 /bin/bash < ./localscript.sh
Warning private system unauthorized users will be prosecuted.
username#1.2.3.4's password:
bash: localscript.sh: No such file or directory
Warning private system unauthorized users will be prosecuted.
username#1.2.3.4's password:
scp: /home/some/file/*: No such file or directory
Any feedback on the same would be appreciated, I think the first approach might be a feasible solution, except the fact that spawn is too fast and none of the 'sleep' or 'after' commands are helping/working. I think the second approach is also valid however it seems like there is a different way of running a local script on a remote server than the usual way we do on Linux when using 'expect'.
Sorry for so much elaboration, I am hoping to be out of my misery soon :)
Indeed the timeout you are setting is not working as you expect it to. Both scripts are spawned, and the expect "assword:" after each spawn is actually catching and reacting to the same password prompt.
expect is actually more sophisticated than a cursory glance would lead you to believe. Each spawn should return a PID, which you can use with your expect to look for output from a specific process.
expect can also be broken down into multiple parts, and have the ability to define subroutines. Here are some more advanced use examples https://wiki.tcl-lang.org/10045
In this specific case I would suggest waiting for the scp to complete before spawning the next process.
expect {
"assword:" {
send "$password\r"
exp_continue # Keep expecting
}
eof {
puts -nonewline "$expect_out(buffer)"
# eof so the process should be done
# It is safe to execute the next spawn
# without exp_continue this expect will
# break and continue to the next line
}
}

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.

What's the best way to mix remote expect scripts and local bash commands?

I'm automating tasks on a local and remote machine (behind a firewall). Once I'm done with tasks on the remote machine, I'd like the script to return to executing commands on the local machine.
#!/usr/bin/expect -f
set timeout -1
spawn ssh username#host
expect "Password: "
send "mypassword\r"
expect "username#host:~$"
...do some stuff...
send "exit\r"
expect eof
[then, once on the local machine, change directories and do other things]
What's the best way to append bash commands? I suppose I could start with bash, call expect within it, then simply return to bash once expect is done.
Expect is based on Tcl, so it can run the same commands. But if your goal is to run bash commands, the best bet is to run them from bash as a separate script, exactly as you propose in your last sentence.
It really depends on what your idea of ...do some stuff... is. Here's an example of something I recently did from my OSX w/s to an AWS instance
export all_status
init_scripts=($(ssh -q me#somehost 'ls /etc/init.d'))
for this_init in ${init_scripts[#]};do
all_status="${all_status}"$'\n\n'"${this_init}"$'\n'"$(ssh -q somehost \'sudo /etc/init.d/${this_init} status\')"
done
echo "$all_status" > ~/somehost_StatusReport.txt
unset all_status
Passing a command at the end of the ssh command will cause the command to be run on the remote host. Or you can scp a script to the remote host and run it with
ssh somehost '/home/me/myscript'
I met this situation recently too. I make a shell supexpect.sh which could login and execute command automatically. It will return to your local shell at the end.
#!/usr/bin/expect
#Usage:supexpect <host ip> <ssh username> <ssh password> <commands>
set timeout 60
spawn ssh [lindex $argv 1]#[lindex $argv 0] [lindex $argv 3]
expect "yes/no" {
send "yes\r"
expect "*?assword" { send "[lindex $argv 2]\r" }
} "*?assword" { send "[lindex $argv 2]\r" }
send "exit\r"
expect eof
To execute:
./supexpect.sh 10.89.114.132 username password "ls -a;pwd;your_stuff_on_remote_host"
Note:
The prompt might need to adapt to your own system, and of course you need to pass execute permission to it.

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
}

How to write bash script that enters password after the first command? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Using expect to pass a password to ssh
I want to have ssh connection to a remote machine and instead of using ssh command along with the machine address and password, I just want to write a little function that executes the ssh command to the machine and enters the pass after the server asks it. I can write the ssh part but how can I make the script that enters also the pass when the host ask for it?
You may use expect script. You can pass arguments from cmd line. Sample code I write:
#!/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 $command
#puts $command
expect {
"(yes/no)?"
{
send "yes\n"
expect "*assword:" { send "$password\n"}
}
"*assword:"
{
send "$password\n"
}
}
You can use Expect tool
It's exactly what you need:
EDIT
#!/usr/bin/expect
set timeout 60
set user "yourName"
set machine "nameOfYourMachine"
set password "yourPassword"
set command "command that you want execute via ssh"
spawn ssh $user#$machine
while {1} {
expect {
eof {break}
"The authenticity of host" {send "yes\r"}
"password:" {send "$password\r"}
"*\]" {send "exit\r"}
"bash" {send "$command"}
}
}
wait
close $spawn_id
Just workaround it as you need

Resources