Use Expect in Shell Script

Use Expect in a Bash script to provide a password to an SSH command

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.

How to use expect inside bash script

The bash script you have defined is passing the expect commands on the standard input of expect. However, the expect command requires its arguments on a file or as an argument using the -c option.

You have several options but to add the less modifications on your script you just need to use the process substitution to create a here-document (temporary) for the expect command.

#!/bin/bash 

echo "[DEBUG] INIT BASH"

cd /home
touch somefile

/usr/bin/expect <(cat << EOF
spawn scp -r -P remoteServerPort somefile remoteServerIP:/home
expect "Password:"
send "MyPassWord\r"
interact
EOF
)

echo "[DEBUG] END BASH"

Expect within bash script

You can use /usr/bin/expect -c to execute expect commands :

#!/bin/bash

/usr/bin/expect -c '

file delete foo.txt

set fh [open foo.txt a]

set servers {xxx@server1 xxx@server2}

foreach s $servers {
spawn ssh $s
expect "password: "
send "PASSWORD\r"
expect "$ "
send "grep "something" /some/log/file.log"
expect "$ " { puts $fh "$expect_out(buffer)"}
send "exit\r"
}

close $fh
'

Embedding an Expect script inside a Bash script

Your Bash script is passing the Expect commands on the standard input of expect. That is what the here-document <<EOD does. However, expect... expects its commands to be provided in a file, or as the argument of a -c, per the man page. Three options are below. Caveat emptor; none have been tested.

  1. Process substitution with here-document:

    expect <(cat <<'EOD'
    spawn ... (your script here)
    EOD
    )

    The EOD ends the here-document, and then the whole thing is wrapped in a <( ) process substitution block. The result is that expect will see a temporary filename including the contents of your here-document.

    As @Aserre noted, the quotes in <<'EOD' mean that everything in your here-document will be treated literally. Leave them off to expand Bash variables and the like inside the script, if that's what you want.

  2. Edit Variable+here-document:

    IFS= read -r -d '' expect_commands <<'EOD'
    spawn ... (your script here)
    interact
    EOD

    expect -c "${expect_commands//
    /;}"

    Yes, that is a real newline after // - it's not obvious to me how to escape it. That turns newlines into semicolons, which the man page says is required.

    Thanks to this answer for the read+heredoc combo.

  3. Shell variable

    expect_commands='
    spawn ... (your script here)
    interact'
    expect -c "${expect_commands//
    /;}"

    Note that any ' in the expect commands (e.g., after id_rsa) will need to be replaced with '\'' to leave the single-quote block, add a literal apostrophe, and then re-enter the single-quote block. The newline after // is the same as in the previous option.

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

You are doing it wrong in two means:

  1. If you want expect to interact with ssh, you need to start ssh from expect script and not before.

  2. 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

Error using expect to execute shell script

expect eof is still subject to the timeout. The script is probably taking longer than 10 seconds to complete. I'd suggest set timeout -1


Obviously I don't know what the script is doing, but maybe you don't need expect for this: Try sending the responses to the script's stdin

printf '%s\n' "Seattle" "{{ elevated }}" "{{ elevated_pass }}" | sudo ./addto-AD

How to use expect in Shell script like nested If Else loop?

First, you're missing the Command Substitution syntax to execute the expect code:

model=$(expect -c ...)
# ....^^.............^

Next, to optionally expect patterns, you need the expect {patt1 action1 patt2 action2 ...} form:

expect -c '
spawn ssh username@'"$ip"' "show version | in cisco"
expect {
-re "The.*(yes/no)?" {send "yes\r"; exp_continue}
-re ".*UNAUTH.*password:" {send "password\r"; exp_continue}
eof
}
'

That way, expect can match any of the patterns. The exp_continue command "loops" within the same expect command so you can match more than one of them. The eof pattern matches when ssh connection closes after the "show version ..." command has finished.

Newlines for readability.

Putting this together:

model=$(
expect -c '
spawn ssh username@'"$ip"' "show version | in cisco"
expect {
-re "The.*(yes/no)?" {send "yes\r"; exp_continue}
-re ".*UNAUTH.*password:" {send "password\r"; exp_continue}
eof
}
' | grep -i cisco
)

I have a feeling that there's more you need to do in the grep part, but you didn't show the output of just the expect command.


update:

  1. use spawn -noecho ssh ... so expect will not print the spawn command.
  2. then, you'll get whatever output ssh needs to show for the login process, and then the "show" command output:

    • if you're expecting exactly 1 line of output, you might want to change grep to tail -n 1.
    • otherwise, show the output you get and we can help you filter out the noise.

update 2: filtering out the noise

I'm going to assume that the regex pattern cisco (.*) processor is what you need to match:

model=$(
expect -c '
log_user 0
spawn ssh username@'"$ip"' "show version | in cisco"
expect {
-re "The.*(yes/no)?" {send "yes\r"; exp_continue}
-re ".*UNAUTH.*password:" {send "password\r"; exp_continue}
-re "cisco (.*) processor" {puts $expect_out(1,string)}
}
expect eof
'
)

log_user 0 turns off the spawned process's ability to write to stdout. Expect can still capture its output though.



Related Topics



Leave a reply



Submit