-   Linux - General (
-   -   expect command ssh sudo error (

linuxguru1 04-27-2013 05:04 PM

expect command ssh sudo error
Hello. First time posting. I have written an expect command to ssh and sudo to a server and run a command.
I must write this code to allow for any variances or errors from the user (Ex, no ssh keys, no passwordless sudo, no sudo privileges, account locked out, wrong username, wrong password, invalid hostname, host not reachable, etc.).
Basically, I must allow for anything that can go wrong - within reason.

The code is working fine with valid and invalid input except for when I setup the user to have password-less sudo "NOPASSWD" in /etc/sudoers. Then I get garbage output which is not what I'm expecting.

I have included "send_user" statement for debugging purposes and they will not be included in the final version.
Here are the variables I use in my script which are prompted for prior to executing expect.


expect –c “
set timeout 2
log_user 0
stty -echo
spawn -noecho ssh -q -o NumberOfPasswordPrompts=1 StrictHostKeyChecking=no -o ConnectTimeout=3 \”${hn}\” -l \”${sshUser}\” -t \“/usr/bin/sudo -k ; /usr/bin/sudo ls -ld /root/Desktop 2>/dev/null\” 2>/dev/null
# Expect ssh password prompt.
expect {
-nocase password: {
send_user \”ssh password was triggered\”
send \”${sshPassword}\”
timeout {
send_user \“ssh timout was triggered\”
eof {
send_user \“ssh eof was triggered\”
# Expect sudo password prompt.
expect {
sudo {
send_user \“sudo password was triggered\”
send \”${sshpassword}\”
@ {
# This never gets executed. Here just in case I ever see the command prompt.
# For testing purposes only.
send_user \”sudo command prompt found\”
timeout {
send_user \“sudo timeout was triggered\”
eof {
send_user \“sudo eof was triggered\”
stty echo
interact;” 2>/dev/null

# Here is the output I receive with "testuser1 ALL=(ALL) ALL" in /etc/sudoers.
[testuser1@testserver1 ~]$ ./expect_script
ssh password was triggeredsudo password was triggered
drwxr-xr-x 2 root root 4096 Mar 29 10:07 /root/Desktop

# Here is the output I receive with "testuser1 ALL=(ALL) NOPASSWD: ALL" in /etc/sudoers.
[testuser1@testserver1 ~]$ ./expect_script
ssh password was triggeredsudo eof was triggered[testuser1@testserver1 ~]$

Since sudo is not prompting for a password eof is found and the script exits. That is fine but I would expect to see the same output as above.

# Here is the same code with error redirection (2>/dev/null), "log_user 0", and "stty -echo" removed.
# "testuser1 ALL=(ALL) ALL" in /etc/sudoers

[testuser1@testserver1 ~]$ ./expect_script
testuser1@testserver1's password: ssh password was triggered
[sudo] password for testuser1: sudo password was triggered
drwxr-xr-x 2 root root 4096 Mar 29 10:07 /root/Desktop

# Here is the same code with error redirection (2>/dev/null), "log_user 0", and "stty -echo" removed.
# "testuser1 ALL=(ALL) NOPASSWD: ALL" in /etc/sudoers

[testuser1@testserver1 ~]$ ./expect_script
testuser1@testserver1's password: ssh password was triggered
drwxr-xr-x 2 root root 4096 Mar 29 10:07 /root/Desktop
sudo eof was triggeredspawn_id: spawn id exp4 not open
while executing

As you can see I receive the output but other errors as well. I cannot simply grep for what I want because any command can be substitued for "ls -ld /root/Desktop". I won't know what to look for.
I was reading about the expect buffer but not sure if that is my answer.
Any help is appreciated.

lleb 04-28-2013 10:23 AM

i am very new to the expect/autoexpect (have never liked it for p/w as it puts the p/w in plane text) but from what i have been reading the -c " you have for the expect is incomplete you have either no closing " or you have no command. also please use code/code flags this makes life much simpler when reading code. so example:

for the opening code... [ code ] without the spaces and [ / code ] without the spaces to end:


# echo "Hello world."
# Hello wold.

according to the man page for expect:


The -c flag prefaces a command to be executed before any in the script.
The command should be quoted to prevent being broken up by the shell.
This option may be used multiple times. Multiple commands may be executed
 with a single -c by separating them with semicolons. Commands are executed
 in the order they appear. (When using Expectk, this option is specified as

like so:

expect -c "strace 4" script.exp

you only have:

expect –c “
with no command and no closing " to start your script. from what little bash i know this will cause issues as the -c " will continue until it finds an other " to close the command and attempt to run all of them as bash commands. that will be an issue.

from an other forum i just found, this might help

You cannot put your expect script coding inside a bash shell script. Bash will not understand expect commands.

You can call the expect utility using "expect -c" command as shown here:


expect -c "
spawn sftp $username@$hostname
expect \"password:\"
send \"$password\r\"

note the last line is a " to close the -c option for expect.


.. or you create a separate expect script and invoke it as following:

./sftp.exp "$username" "$hostname" "$password"
set username [lindex $argv 0]
set hostname [lindex $argv 1]
set password [lindex $argv 2]
spawn sftp $username@$hostname
expect *password:
send "$password\r"

Not sure if that helps or not, but you might also want to play around with autoexpect to create a file for you. I played around with it to generate a script.exp for ssh with p/w into my iMAC. works well enough without issue. Not sure if those will help, but I hope they do. 04-28-2013 10:27 AM

Since you expect a password prompt and the erroneous entry is the one in which you do not require passwords, I'd expect it to be because no password prompt is shown. Ref:

Small tip: add the extra verbose flag to your shebang to debug scripts (at the top):

#!/bin/bash -xv
Then you can run the script and see what happens:)

linuxguru1 04-29-2013 07:13 AM

Thanks for the tips guys. I think my problem is the linear fashion of my expect statements. After nesting the expect statements and removing the sudo command from the initial ssh session I am getting better results. Basically, I check if ssh failed or not and if successful then I send the sudo command. This way I can control the errors much better. I will post my results probably in a few days if anyone is interested. Thanks for all your help.

lleb 04-29-2013 08:09 AM

glad to help, yes i would be interested in your final script.

linuxguru1 05-07-2013 03:57 PM

Final script
I was finally able to get a good working version of my script. A little background - I created a script to get the firmware from a server. I setup the script to walk through an IBM Blade Center and grab the hostnames in the description fields so it can ssh to the server and get the firmware levels. I have included some test cases (embedded after expect command) in case anyone else is trying to do this too. I have included the whole bash function I use in my script. In the function, I only care to retrieve the correct result, if it fails I just want it to return nothing. For anyone copying this you can add send_user commands to get output for the errors if you desire.

## Retrieve Chassis Blade Firmware.
get_chassis_blade_fw() {
export hn_blade=$(snmpwalk -r 10 -Ov -Oq -v $secVersion -u $secName -l $secLevel -a $authProtocol -A $authPassword -x $privProtocol -X $privPassword $hn$1 2>/dev/null | sed 's/"//g')
local chassis_blade_fw=""
local answer=()
export sshUser
export sshPassword
export prompt="${sshUser}@${hn_blade}"
export cmd="/usr/sbin/lsfw ${chassis_blade_options} 2>/dev/null"

# Check for conditions.
[[ "${hn_blade}" = '(No name)' ]] && chassis_blade_fw="EMPTY"
[[ "${hn_blade:0:5}" = "Error" ]] && chassis_blade_fw="N/F"
[[ "${hn_blade:0:7}" = "timeout" ]] && chassis_blade_fw="N/F"
[[ "${hn_blade:0:8}" = "snmpwalk" ]] && chassis_blade_fw="N/F"
[[ "${hn_blade:0:14}" = 'No Such Object' ]] && chassis_blade_fw="N/F"
[[ "${hn_blade:0:16}" = 'No Such Instance' ]] && chassis_blade_fw="N/F"
[[ -z "${hn_blade}" ]] && chassis_blade_fw="N/F"

if [[ "${chassis_blade_fw}" != "EMPTY" && "${chassis_blade_fw}" != "N/F" ]] ; then
#### Test cases
# Wrong ssh password/username: Returns null.
# Invalid username: Returns null.
# Host invalid/not reachable: Returns null.
# Account locked out: Returns null.
# Account requires password change: Returns null.
# "Your LDAP password will expire in ? days" message: Failed testing. Don't plan to fix.
# Last login message: Filtered by expect.
# "There were ? failed login attempts since the last successful login" message: Filtered by expect.
# Login banner: Filtered by expect.
# ssh_key_exchange failure message:+ (fixed with ssh -q) Returns null.
# ssh keys are setup: (fixed with PubkeyAuthentication=no) Works as expected
# Wrong sudo password: (N/A since same as ssh pwd and ssh will fail) N/A
# No sudo privileges: Returns null
# sudo with NOPASSWD: Works as expected
# Invalid command: Requires 2>/dev/null in $cmd and returns null

#### Caveats
# Must use short hostname is AMM blade description field.

chassis_blade_fw=$(/usr/bin/expect -c '
set sshUser $env(sshUser)
set sshPassword $env(sshPassword)
set hn $env(hn_blade)
set prompt $env(prompt)
set cmd $env(cmd)

#exp_internal 1
set timeout 2
set send_human {.1 .3 1 .05 2}
match_max 100000
log_user 0
stty -echo

spawn -noecho ssh -q -o PubkeyAuthentication=no -o NumberOfPasswordPrompts=1 -o StrictHostKeyChecking=no -o ConnectTimeout=3 $hn -l $sshUser -t

expect {
default { exit 1 }
-nocase password: { send "$sshPassword\r" }

expect {
default { exit 1 }
-re ${prompt} {
send "/usr/bin/sudo $cmd\r"
expect full_buffer { exp_continue } ; # Force all output before continuing.
-re (expired|change) { exit 1 }

expect {
default { exit 1 }

# Sudo requires password.
"password for $sshUser: " {
send "$sshPassword\r"

expect -indices -re $prompt
set sudo_out [string range $expect_out(buffer) 0 [expr $expect_out(0,start) -1]] ; # Retrive only expected output.

# Trim garbage output.
set sudo_out [string trimleft "$sudo_out" " \r\n"]
set sudo_out [string trimright "$sudo_out" "\r\n\u001b\]0\;${prompt}:~\u0007\["]
send_user [string trimright "$sudo_out" "\r\n\u001b\]0\;"]

# Sudo does not require a password (from sudo grace period or NOPASSWD).
-re ".*${cmd}(.*)${prompt}" {
set sudo_out $expect_out(1,string)

# Trim garbage output.
set sudo_out [string trimleft "$sudo_out" " \r\n"]
set sudo_out [string trimright "$sudo_out" "\r\n\u001b\]0\;${prompt}:~\u0007\["]
send_user [string trimright "$sudo_out" "\r\n\u001b\]0\;"]

stty echo
interact;' 2>/dev/null)

# Check for conditions.
[[ -z "${chassis_blade_fw}" ]] && chassis_blade_fw="${hn_blade} (N/R)"

# Return results.
echo "${chassis_blade_fw}"
} # END get_chassis_blade_fw()

You may notice most of the test cases were satisfied by ssh options. One problem with this script I need your help on is how do I wait for all the output after sending a command before it starts matching the expect statements? When I send the sudo command it starts matching right away that is why I have the full_buffer line so it will not match and if the sudo command requires a password it will see the prompt. Without full_buffer it will never match the sudo prompt.


linuxguru1 05-07-2013 03:59 PM

Can someone tell me how to keep the formatting in the code? It looks horrible after I hit send.

lleb 05-07-2013 04:40 PM

use [ code ] and [ / code ] without the spaces and that will do the trick.

All times are GMT -5. The time now is 06:17 PM.