how to use expect in linux? [duplicate] - linux

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

Related

Using 'expect' command to pass password to SSH running script remotely

I need to create a bash script that will remotely run another script on a batch of machines. To do so I am passing a script through SSH.
ssh -p$port root#$ip 'bash -s' < /path/to/script/test.sh
I thought it would use my RSA keys but I am getting error:
"Enter password: ERROR 1045 (28000): Access denied for user 'root'#'localhost' (using password: YES)"
I tried using sshpass to no avail. So my next solution was using expect. I have never used expect before and I'm positive my syntax is way off.
ssh -p$port root#$ip 'bash -s' < /path/to/script/test.sh
/usr/bin/expect <<EOD
expect "password"
send "$spass\n"
send "\n"
EOD
I have root access to all machines and ANY solution will do as long as the code remains within bash. Just keep in mind that this will be done in a loop with global variables ($spass, $ip, $port, etc) passed from a parent script.
You are doing it wrong in two means:
If you want expect to interact with ssh, you need to start ssh from expect script and not before.
If you put the script (/path/to/script/test.sh) to stdin of ssh, you can't communicate with the ssh process any more.
You should rather copy the script to remote host using scp and then run it.
Expect script might look like this:
/usr/bin/expect <<EOF
spawn ssh -p$port root#$ip
expect "password"
send "$Spass\r"
expect "$ "
send "/path/to/script/on/remote/server/test.sh\r"
expect "$ "
interact
EOF
#!/usr/bin/expect
#Replace with remote username and remote ipaddress
spawn /usr/bin/ssh -o StrictHostKeyChecking=no username#IPAddress
#Replace with remote username and remote ipaddress
expect "username#IPAddress's password: "
#Provide remote system password
send "urpassword\n"
#add commands to be executed. Also possible to execute bash scripts
expect "$ " {send "pwd\n"} # bash command
expect "$ " {send "cd mytest\n"}
expect "$ " {send "./first.sh\n"} # bash scripts
expect "$ " {send "exit\n"}
interact

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.

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.

Expect not working inside my Bash script

I am trying to execute expect command inside by small bash script to login into servers using key authentication method. My script is as follows:
#!/bin/bash
HOST=$1
/usr/bin/expect -c "
spawn ssh -i /root/.ssh/id_rsa root#$HOST
expect -exact "Enter passphrase for key '/root/.ssh/id_rsa': " ;
send "PASSPHRASE\n" ;
interact
"
Output with error is:
spawn ssh -i /root/.ssh/id_rsa root#server.domain.com
couldn't read file "passphrase": no such file or directory
Can you help to correct this?
There is a quoting issue in your code. Rather than trying to pass commands to expect on the command line save your code as an Expect script. You can then run it from a shell script or otherwise.
script.exp
#!/usr/bin/expect
# usage: ./script.exp host
set HOST [lindex $argv 0]
spawn ssh -i /root/.ssh/id_rsa root#$HOST
expect -exact "Enter passphrase for key '/root/.ssh/id_rsa': "
send "PASSPHRASE\n"
interact
However, if that's all you're doing with Expect in this case I second the suggestion to use ssh-agent instead.

Cannot use expect in Bash script

In my bash script file, I try to use expect to provide password for ssh command but it doesn't work. Here is my script:
#!/bin/bash
/usr/bin/expect << EOD
spawn ssh root#192.168.1.201
expect "root#192.168.1.201's password:"
send "mypassword\r"
interact
expect eof
EOD
And the output after I execute the script:
[oracle#BTMVNSRV191 Desktop]$ ./login.sh
spawn ssh root#192.168.1.201
root#192.168.1.201's password: [oracle#BTMVNSRV191 Desktop]$
Could someone let me know, how to use expect in my script without changing #!/bin/bash to #!/usr/bin/expect?
The following works as a single line of bash script in OS X Terminal. It was only intended for use on a firewall protected LAN. Further details at my original post.
expect -c 'spawn ssh -o StrictHostKeyChecking=no remote-user#remote-IP;
expect assword; send remote-password\r; expect remote-user$;
send "sudo shutdown -h +1\r"; expect assword; send remote-password\r; interact'
ssh (and any other password reading tool) reads its password not from its standard input. It uses some tricky ioctl()-s on its terminal device. This is because you can't give them your password in a pipe.
It is not really a big problem, because widely used cleartext passwords caused more harm as if sometimes we need to find some alternative, password-less solution.
In cases of the ssh, there is a very simple thing for that. Google for ssh-keygen. I suggest to use that, configure a passwordless ssh and everything will be fine.

Resources