How to have expect timeout when trying to login to an ssh session it has spawned?

32,355

First of all I would use an expect script for this and lose the bash scripting.

Then for the expect part: You can do this by using a switch that also matches for timeout (page 12 of exploring expect). In that way you can explicitly have some action when expect timeouts.

Otherwise by setting the timeout it will just continue with the next command in line.

set timeout 60
expect {
 "assword:" {
 }
 timeout {
    exit 1 # to exit the expect part of the script
 }
}

I've created something similar where I used an overall expect script to run an expect script in parallel.

multiple.exp

#!/bin/sh
# the next line restarts using tclsh \
exec expect "$0" "$@"

# multiple.exp --
#
#    This file implements the running of multiple expect scripts in parallel.
#    It has some settings that can be found in multiple.config
#
# Copyright (c) 2008
#
# Author: Sander van Knippenberg

#####
# Setting the variables
##

source [file dirname $argv0]/.multiple.config

# To determine how long the script runs
set timingInfo("MultipleProcesses") [clock seconds]

# ---------------------------------------------------------------------

######
# Procedure to open a file with a certain filename and retrieve the contents as a string 
#
# Input: filename
# Output/Returns: content of the file
##
proc openFile {fileName} {
    if {[file exists $fileName] } {
        set input [open $fileName r]
    } else {
        puts stderr "fileToList cannot open $fileName"
        exit 1
    }
    set contents [read $input]
    close $input
    return $contents
}

######
# Procedure to write text to a file with the given filename
#
# Input: string, filename
##
proc toFile {text filename} {
    # Open the filename for writing
    set fileId [open $filename "w"]

    # Send the text to the file.
    # Failure to add '-nonewline' will reslt in an extra newline at the end of the file.
    puts -nonewline $fileId $text

    # Close the file, ensuring the data is written out before continueing with processing
    close $fileId
}

# ---------------------------------------------------------------------

# Check for the right argument
if {$argc > 0 } {
    set hostfile [lindex $argv 0]
} else {
    puts stderr "$argv0 --- usage: $argv0 <hosts file>"
    exit 1
}

# Create the commands that can be spawned in parallel
set commands {}

# Open the file with devices
set hosts [split [openFile $hostfile] "\n"]

foreach host $hosts {
        if { [string length $host] > 1 } {
            lappend commands "$commandDir/$commandName $host"  # Here you can enter your own command!
        }
}

#  Run the processes in parallel
set idlist {}
set runningcount 0
set pattern "This will never match I guess"

# Startup the first round of processes until maxSpawn is reached, 
# or the commands list is empty.
while { [llength $idlist] < $maxSpawn && [llength $commands] > 0} {
    set command [lindex $commands 0]
    eval spawn $command 
    lappend idlist $spawn_id
    set commands [lreplace $commands 0 0]
    incr runningcount
    set commandInfo($spawn_id) $command   
    set timingInfo($spawn_id) [clock seconds]
    send_user "      $commandInfo($spawn_id) - started\n"
}

# Finally start running the processes
while {$runningcount > 0} {
    expect {
        -i $idlist $pattern {
        }
        eof {
            set endedID $expect_out(spawn_id)
            set donepos [lsearch $idlist $endedID]
            set idlist [lreplace $idlist $donepos $donepos]
            incr runningcount -1
            set elapsedTime [clock format [expr [clock seconds] - $timingInfo($endedID)] -format "%M:%S (MM:SS)"]

            send_user "      $commandInfo($endedID) - finished in: $elapsedTime\n"

            # If there are more commands to execute then do it! 
            if {[llength $commands] > 0} {
                set command [lindex $commands 0]
                eval spawn $command             
                lappend idlist $spawn_id
                set commands [lreplace $commands 0 0]
                incr runningcount
                set commandInfo($spawn_id) $command            
                set timingInfo($spawn_id) [clock seconds]
           }
        }
        timeout {
            break
        }
    }
}
set elapsed_time [clock format [expr [clock seconds] - $timingInfo("MultipleProcesses")] -format "%M:%S (MM:SS)"] 
send_user "$argv0 $argc - finished in: $elapsedTime\n"

multiple.config

# The dir from where the commands are executed.
set commandDir "/home/username/scripts/expect/";
set commandName "somecommand.exp";

# The maximum number of simultanious spawned processes.
set maxSpawn 40;

# The maximum timeout in seconds before any of the processes should be finished in minutes
set timeout 20800;
Share:
32,355
dunxd
Author by

dunxd

I'm currently freelance specialising in international connectivity and infrastructure working with clients in the humanitarian space. If your organisation struggles to work effectively because of limited internet options in far flung locations, maybe I can help. Until 2017 I worked at a large international development charity in London, as International Operations Manager. I managed a team of Regional ICT Service Managers, based in developing world countries, who kept the users happy through fixing problems, setting up great connectivity and generally making sure users could do their day jobs. I think I did a good job as a manager - some of my team went on to great things! I previously worked at the same place as International Network Systems Analyst. I looked after a bunch of ICT systems in offices in the developing world, as well as looking after systems in our HQ. I gained a lot of knowledge in that job, and the techy side competes with the people stuff in the new role, hence I still hang out here a lot. I'm passionate about the use of ICT in developing countries, both in terms of dealing with the inherent problems for ICT in those places, and using ICT as a tool for development.

Updated on December 20, 2020

Comments

  • dunxd
    dunxd over 3 years

    I am writing an bash script that uses expect to login to a bunch of Cisco ASAs (they don't support certificate login, hence using expect), makes a change to the configuration and then logs out.

    I'd like the script to move onto the next ASA if it is unable to login.

    Here is the script:

    #!/bin/bash
    # Scriptname: set-mtu
    for asa in $(cat asa-list-temp)
    do
            /usr/bin/expect << EndExpect
                    spawn ssh admin_15@$asa
                    expect "assword:"
                    send "pa$$w0rd\r"
                    expect ">"
                    send "do something\r"
                    expect ">"
                    send "exit\r"
    EndExpect
    done
    

    I think I can set a timeout on expect "assword:" but I can't figure out how to get it to close the spawned ssh session and then move onto the next ASA in the for list.

  • dunxd
    dunxd almost 13 years
    Kind of helpful, but I don't own Exploring Expect, and I don't really want to rewrite my short script into a much longer one in a language I don't yet know.
  • Sander van Knippenberg
    Sander van Knippenberg almost 13 years
    The first bit will do what you request. The second bit is a hint as to how you can optimize what you want to do using expect. Exploring expect can be found here: my.safaribooksonline.com/9781565920903/…