#!/usr/bin/expect # This file provides many valuable expect procedures like handle_timeout and # handle_eof. my_source [list print.tcl] proc handle_timeout { description } { # Print timeout error message to stderr and exit 1. # Description of argument(s): # description A description of what was being expected # (e.g. "an SOL login prompt"). global spawn_id global expect_out set timeout [get_stack_var timeout {} 2] if { $timeout == 1 } { set seconds "second" } else { set seconds "seconds" } puts stderr "" print_error "Did not get ${description} after $timeout ${seconds}.\n" # Using uplevel to be able to access expect_out. if { [ catch {uplevel { puts stderr [sprint_var expect_out]}} result ] } { puts stderr [sprint_varx expect_out ""] } # If caller has exit_proc defined, call it. Otherwise, just call exit. if { [info procs "exit_proc"] != "" } { exit_proc 1 } exit 1 } proc handle_eof { description } { # Print end-of-file error message to stderr and exit 1. # Description of argument(s): # description A description of what was being expected # (e.g. "an SOL login prompt"). global spawn_id puts stderr "" print_error "Reached end of file before getting $description.\n" # Using uplevel to be able to access expect_out. if { [ catch {uplevel { puts stderr [sprint_var expect_out]}} result ] } { puts stderr [sprint_varx expect_out ""] } # If caller has exit_proc defined, call it. Otherwise, just call exit. if { [info procs "exit_proc"] != "" } { exit_proc 1 } exit 1 } proc expect_wrap {pattern_list message {timeout 15} {fail_on_timeout 1}} { # Run the expect command for the caller and return the list index of the # matching pattern. # This function offers the following benefits over calling the expect # command directly: # - It makes program debug easier. When the program is run with --debug=1, # this function prints useful debug output. # - It will do standardized timeout and eof handling. # Description of argument(s): # pattern_list A list of patterns to be matched. If one # of the patterns matches, the list index of # the matching item will be returned. By # default, each pattern is presumed to be a # regex. If the caller wishes to, they may # precede each pattern with either of the # following: "-re ", "-gl " or "-ex " in # order to explicitly choose the kind of # match to be done.. # message A message explaining what is being # expected (e.g. "an SOL login prompt"). # This will be included in output messages. # timeout The expect timeout value. # fail_on_timeout A flag governing the behavior when the # expect command results in a timeout. If # set to 1, this procedure will print an # error message to standard error and exit # the program with a non-zero return code. # If set to 0, it will return # [expect_wrap_timeout]. # Example usage: # set result [expect_wrap\ # [list $bad_user_pw_regex "sh: xauth: command not found"]\ # "an SOL prompt" 10] # # switch $result { # 0 { # puts stderr "" ; print_error "Invalid username or password.\n" # exit_proc 1 # } # 1 { # dict set state ssh_logged_in 1 # } # } global spawn_id global expect_out # Recognized flags. set flags [list "-re" "-ex" "-gl"] # This helps debug efforts by removing leftover, stale entries. array unset expect_out \[1-9\],string # Prepare the expect statement. append cmd_buf "global spawn_id\n" append cmd_buf "global expect_out\n" append cmd_buf "expect {\n" set ix 0 foreach pattern $pattern_list { # Check to see whether the caller has specified a flag (e.g. "-re", # "-ex", etc.) at the beginning of the pattern. set tokens [split $pattern " "] if { [lsearch $flags [lindex $tokens 0]] != -1 } { # Caller specified a flag. set flag [lindex $tokens 0] # Strip the flag from the pattern. set pattern [string range $pattern 4 end] } else { set flag "-re" } append cmd_buf " ${flag} {$pattern} {set expect_result $ix}\n" incr ix } if { $fail_on_timeout } { append cmd_buf " timeout {handle_timeout \$message}\n" } else { append cmd_buf " timeout {set expect_result \[expect_wrap_timeout\]}\n" } append cmd_buf " eof {handle_eof \$message}\n" append cmd_buf "}\n" dprint_timen "Expecting $message." dprint_issuing "\n${cmd_buf}" eval ${cmd_buf} dprintn ; dprint_vars expect_out expect_result return $expect_result } proc expect_wrap_timeout {} { # Return constant value of 1000. return 1000 } proc send_wrap {buffer {add_lf 1}} { # Send the buffer to the spawned process. # This function offers the following benefits over calling the send command # directly: # - It makes program debug easier. When the program is run with --debug=1, # this function prints useful debug output. # Description of argument(s): # buffer The string to be sent to the spawned # process. # add_lf Send a line feed after sending the buffer. # Example usage. # Close the ssh session. # send_wrap "~." # # set expect_result [expect_wrap\ # [list "Connection to $host closed"]\ # "a connection closed message" 5] global spawn_id global expect_out set cmd_buf "send -- {${buffer}}" dprint_issuing eval ${cmd_buf} if { $add_lf } { send -- "\n" set cmd_buf "send -- \"\\n\"" dprint_issuing eval ${cmd_buf} } } proc shell_command {command_string {prompt_regex} { quiet {} } \ { test_mode {} } { show_err {} } { ignore_err {} } {trim_cr_lf 1}} { # Execute the command_string on the shell command line and return a list # consisting of 1) the return code of the command 2) the stdout/stderr. # It is the caller's responsibility to spawn the appropriate process # (ssh,telnet) and to get the process to a shell command line (by logging # in, etc.). # Description of argument(s): # command_string The command string which is to be run on # the shell (e.g. "hostname" or "grep this # that"). # prompt_regex A regular expression to match the prompt # for current shell to run on (e.g "/ #"). # quiet Indicates whether this procedure should # run the print_issuing() procedure which # prints "Issuing: " to stdout. # The default value is 0. # test_mode If test_mode is set, this procedure will # not actually run the command. If # print_output is set, it will print # "(test_mode) Issuing: " to # stdout. The default value is 0. # show_err If show_err is set, this procedure will # print a standardized error report if the # shell command returns non-zero. The # default value is 1. # ignore_err If ignore_err is set, this procedure will # not fail if the shell command fails. # However, if ignore_err is not set, this # procedure will exit 1 if the shell command # fails. The default value is 1. # trim_cr_lf Trim any trailing carriage return or line # feed from the result. # Set defaults (this section allows users to pass blank values for certain # args). set_var_default quiet [get_stack_var quiet 0 2] set_var_default test_mode 0 set_var_default show_err 1 set_var_default ignore_err 0 set_var_default acceptable_shell_rcs 0 global spawn_id global expect_out qprintn ; qprint_issuing ${command_string} ${test_mode} if { $test_mode } { return [list 0 ""] } send_wrap "${command_string}" set expect_result [expect_wrap\ [list "-ex $command_string"]\ "the echoed command" 5] set expect_result [expect_wrap\ [list {[\n\r]{1,2}}]\ "one or two line feeds" 5] # Note the non-greedy specification in the regex below (the "?"). set expect_result [expect_wrap\ [list "(.*?)$prompt_regex"]\ "command output plus prompt" -1] # The command's stdout/stderr should be captured as match #1. set out_buf $expect_out(1,string) if { $trim_cr_lf } { set out_buf [ string trimright $out_buf "\r\n" ] } # Get rc via recursive call to this function. set rc 0 set proc_name [get_stack_proc_name] set calling_proc_name [get_stack_proc_name -2] if { $calling_proc_name != $proc_name } { set sub_result [shell_command {echo ${?}} $prompt_regex 1] dprintn ; dprint_list sub_result set rc [lindex $sub_result 1] } if { $rc != 0 } { if { $show_err } { puts stderr "" ; print_error_report "The prior shell command failed.\n" } if { ! $ignore_err } { if { [info procs "exit_proc"] != "" } { exit_proc 1 } } } return [list $rc $out_buf] }