A Shell Script to Customize Parameters for an expect Script The first script obtains the user input necessary to connect to the desired terminal servers and perform the intended tasks..
Trang 1■ ■ ■
Automating User Input with
expect
The expect utility’s name suggests precisely what it does: “expect” some output from
an interactive program, and send the program some input in response expect has much
more functionality than I cover in this chapter, but this chapter provides a good example
of how it can be used To find more complete information, you can consult the expect
manual page
You may find that when you try to automate a task, the utilities or tools you are using
don’t lend themselves well to scripting In the past, use of the format or fdisk command
(along with many others) was difficult to automate Today we have versions of these
utilities, such as sfdisk, that are much easier to use within a script A more modern use
of expect might include logging into specialized hardware to gather information or to
customize settings, as is required when administering network routers, switches, and
firewalls
This chapter presents a pair of scripts for automating the control of a serial terminal
server This is a type of network-accessible hardware that looks very much like a network
hub or a switch with multiple RJ45 ports Each physical port can be connected to serial
devices, such as serial consoles Once consoles are attached to the terminal server, you
can telnet to a specific network port on the terminal server and establish a connection
with the attached console
The first example in this chapter is a shell script that processes user-provided
command-line switches that specify what commands to send to the terminal server
The second script, which is called by the first, is an expect script that performs all the
manual labor expect is an extension of the Tcl scripting language expect was designed
to communicate with an interactive program, and it works well with ssh, telnet, ftp,
and other interactive utilities
Trang 2A Shell Script to Customize Parameters for an expect Script
The first script obtains the user input necessary to connect to the desired terminal server(s) and perform the intended tasks It displays usage instructions and allows the user to specify a specific terminal server or to provide a file containing node names if there are multiple terminal servers with which the user wants to communicate in the same way and at the same time First we need to define a few variables:
#!/bin/sh
NODE=""
CMDS=""
NODEFILE=""
AUTO=""
USAGE="There is a problem with the command, type $0 -h for syntax"
The variables are initialized to null strings, except for the USAGE variable, which contains a message that is displayed whenever the script finds a problem with the command-line call the user provided
The script gets the information it needs from the user on the command line, so we check that switches have been passed
if [ $# -eq 0 ]
then
echo $USAGE
exit 1
fi
If no switches are passed to the script, the script displays the usage statement and quits with a nonzero return code (here, 1)
The next section is where the command-line switches are handled
while getopts idhlc:f:n: opt
do
case $opt in
i) CMDS="$CMDS \"sho ip\""
;;
The code uses the getopts construct, which is explained in greater detail in Chapter 5 The -i switch indicates that the terminal server’s IP settings should be displayed It causes the command sho ip to be appended to the CMDS variable, which holds the commands that will be sent to the terminal server
Next we account for customized commands
c) CUSTOM_CMD=$OPTARG
CMDS="$CMDS \"$CUSTOM_CMD\""
;;
Trang 3The -c switch is for user-provided terminal-server commands that aren’t hard-coded
in the script The user can provide as many such commands as desired when invoking the
shell script, as long as a -c option precedes each command and the command itself is
double-quoted; most commands interpreted by the terminal server contain multiple
words that are space-delimited and so need to be tied together with quotes when the shell
script is called
The OPTARG variable used in handling the -c switch is part of the getopts construct
Note that this switch is followed by a colon in the getopts specification When a colon
follows a switch in the getopts command, getopts will expect some type of argument to
follow that switch whenever it is used OPTARG is the variable that receives the additional
argument to the switch For example, if you had a script that takes a command-line
parameter to specify an optional input file, the invocation might look something like this:
sample_script -f input_file The corresponding getopts line would look like this: while
getopts f:<other switches> opt, and OPTARG would be set to the string "input_file"
The -h switch causes the script to display its usage information
h) cat << EOT
Usage:
$0 [-idhl] [-c "custom command"] [-f node_file] [-n node]
where:
-i Sends the "sho ip" command to the Xyplex terminal server
-d Logs in and drops you to the command prompt
-h Displays this information
-l Logs out ports 1-15
-c Takes a custom command and sends it to the terminal server
Double quotes are required You can have as many of these as you like
-f Defines a file with a list of terminal servers to apply the commands to
-n Defines a specific node to apply the command to
EOT
exit 0
;;
Note that the cat command is used here to format the output, instead of multiple echo
commands Chapter 28 contains more discussion of free-format output using cat
The -d switch in the following code indicates that the terminal-server session is not
automated, and that the user simply wants to be left at a prompt after logging in:
d) AUTO="no"
;;
The presence of this switch causes the AUTO variable to be set to no The expect script
examines this variable, and if it is set to no, the expect script leaves the user at the
com-mand prompt of the terminal server’s shell after logging in, and performs any comcom-mands
specified via the other options before logging out automatically (See the following
sec-tion, “An expect Script to Automate telnet.”) If the AUTO variable is left undefined, the
Trang 4script will perform any specified tasks in an automated fashion without any user
interaction
The -l switch adds a command to tell the terminal server to log out all of its serial ports
l) CMDS="$CMDS \"logout por 1-15\""
;;
On occasion, a terminal server will have a hung and unresponsive serial port A command to log it out resets the port and it becomes usable again The preceding CMDS variable assignment is an example of a command that performs an action on managed hardware This command is specific to the hardware involved
The -f switch specifies a file containing a node list (that is, a list of terminal servers)
f) NODEFILE=$OPTARG
;;
The script loops through the list of terminal servers and performs the specified com-mand(s) against each one
The -n switch indicates that a specific terminal-server node is the target, rather than those in a list of nodes, as specified using the previous switch
n) NODE=$OPTARG
;;
The following are two alternatives for robustness:
?) echo $USAGE
exit 1
;;
*) echo $USAGE
exit 1
;;
esac
done
If anything besides the anticipated options were provided in the invocation of the script, the script should echo the contents of the USAGE variable to the screen, and exit Finally, after processing the switches and building the command list, the script calls the expect script to contact the terminal server If a NODEFILE was specified using the -f switch, it validates the file and then iterates through it, calling the expect script once for each terminal server with the parameters the user supplied If a NODEFILE was not speci-fied by the user via the -f switch, the script validates that an individual terminal server was specified with the -n switch and that the NODE variable is not null If the NODE variable
is null, the expect script is called with the appropriate parameters; otherwise it displays the usage string
if [ "$NODEFILE" != "" ]
then
Trang 5if [ -s $NODEFILE ]
then
for node in `cat $NODEFILE | grep -v '^#'`
do
eval /xyp_connect $NODE $AUTO $LOGNAME $CMDS
done
else
echo There is a problem with $NODEFILE
fi
else
if [ "$NODE" != "" ]
then
eval /xyp_connect $NODE $AUTO $LOGNAME $CMDS
else
echo $USAGE
fi
fi
The eval command is used here to evaluate the variables on that line of code once
before the code is executed This is because the CMDS variable may contain
terminal-server commands that are, as a result of the processing of the switches, surrounded by
backslash-escaped double quotes; these escaped characters must be replaced with
unmodified quotes or else the multiple commands will be read incorrectly as one long
command This is also where the call to the xyp_connect expect script that performs the
interactive functions takes place
An expect Script to Automate telnet
The xyp_connect script, an expect script, performs the communication with the
interac-tive program used to connect to the terminal server, in this case telnet The script starts
out by initializing some variables to hold the parameters that the shell script passed to it
These parameters are accessed by their positions in the argument vector, argv[], of the
expect script’s process The -f switch in the first line of the following code is used so the
script will accept additional command-line options
#!/usr/bin/expect -f
set TERMSERV [lindex $argv 0]
set AUTO [lindex $argv 1]
set USER [lindex $argv 2]
The first parameter is the terminal server to which the expect script will attach The
second parameter defines if this will be an automated session in which the expect script
performs the work, or an interactive one in which the script simply logs you in and leaves
you at the terminal-server shell prompt The third parameter is the user who is to be
logged in
Trang 6The next line of the expect script initiates an interactive telnet session with the termi-nal server
catch {spawn -noecho telnet $TERMSERV 2000}
The spawn command starts by trying to establish a telnet connection at the specified port (2000) Port 2000 is being used because of the way this vendor has designed its equip-ment Other manufacturers will likely be configured differently The noecho switch tells expect to avoid echoing on the user’s console the command that is being spawned
Finally, a catch command surrounds the whole spawn command It catches the output that
is generated by the spawned telnet so that the script can use it later when determining how the telnet command responded
Once the telnet connection has begun, a timeout should be set to check that the com-mand completes within a reasonable amount of time
set timeout 10
expect {
timeout { send_user "Telnet timed out waiting for $TERMSERV\n" ; exit }
"onnection refused" { send_user "Connection was refused to $TERMSERV\n" ; exit } "nknown host" { send_user "System $TERMSERV is unknown\n" ; exit}
"Escape character is '^]'."
}
send "\r"
Here we set the timeout period to 10 seconds; following the setting of the timeout is the first true expect command A single expect command can handle multiple events, per-forming the appropriate task based on which one is detected In this case, a number of responses may be received from an attempted telnet connection A timeout, connection refused, or actual connection are three possibilities For each type, the code needs to determine the appropriate response to make
This first expect command handles the three error events that may arise from the telnet attempt The first event is the timeout Once 10 seconds have passed with no response, the script displays an error message and exits The next two events are repre-sented by patterns matching the error messages that may be caught from the telnet invocation in case of failure: “Connection refused” and “Unknown host,” respectively Because the error message may or may not be initial-capped, depending on the telnet server, and we want to handle both possibilities, the first character is not included in the pattern used to match against the caught output In each event, we use a send_user com-mand to echo the appropriate error output to the user and exit the expect script
If none of these error conditions occur, then we have successfully begun a telnet ses-sion with the terminal server The previous expect command then has no effect, and the script falls through to the next statement, send "\r" But the terminal server does not yet know this Once we are attached to the terminal server, there is no further reply from it
Trang 7until it receives a single carriage return from us This send command delivers that carriage
return, at which point both parties know that we have arrived via telnet at the point just
prior to login Now comes the interaction for the actual login to the terminal server
If expect succeeds in establishing a telnet connection, the caught output consists of
the success string, which for our terminal server is the pound or hash sign, # When the
script detects this response, it proceeds with expect commands, implementing the login
dialogue
Our particular terminal-server hardware will by default take anything for the initial
username and not require a password The expect script here assumes these factory
defaults You may need to change this dialogue to match your environment (For
exam-ple, it would be fairly simple to add another switch to the shell script allowing the
password to be given from the command line, so that the login/password would not
be hard-coded in an unencrypted text file.)
expect "#" { send "access\r" }
expect "username>" { send "$USER\r" }
expect ">" { send "set priv\r" }
expect "Password>" { send "system\r" }
expect ">>"
In our case, the basic login is complete when the > character is received in reply for the
username; however, to perform administrative tasks on the terminal server, we must
upgrade privileges via a set priv command As shown in the preceding code, the default
password for this level of access is system, and once you’re logged in at the privileged level,
you receive a >> prompt
Next we check whether the AUTO variable is set to no Recall that the value of this
vari-able was passed to the expect script as a parameter, and allows the script to determine
whether the user wants to perform a command or a set of commands on the terminal
server, or simply wants to be left logged in to perform her own administration
if { "$AUTO" == "no" } {
send_user "Script ended: You have been dropped to the command line\n"
send "\r"
interact
exit
}
If AUTO is set to no, a message is sent to the user that the script has completed its run and
control of the terminal server session will now be handed over to the user The next-to-last
interact command in this part of the script carries out this handover before exiting
If the script reaches this point, then AUTO has not been set to no, and there may be
terminal-server commands that were intended for the expect script that were included
in the shell script’s command line as described earlier Next we determine the number
of these parameters and assign that value to argc
Trang 8set argc [llength $argv]
for {set i 3} {$i<$argc} {incr i} {
send "[lindex $argv $i]\r"
expect ">>"
}
This code lets us know when to stop looking in the expect script’s argument vector argv for terminal-server commands Each time through the for loop, a terminal-server com-mand is sent; after the comcom-mand finishes running, a >> prompt should be received before the next command is issued (The loop starts at 3 because the first few parameters, at index positions 0, 1, and 2, are those that were used earlier by the expect script: AUTO, TERMSERV, and USER.)
When the list of commands has been processed and all commands have been sent,
we perform the telnet logout dialog
send "^]"
expect "telnet>"
send "quit\r"
send_user "\n"
The first send command in this code segment contains a single special character—not
a caret followed by a right square bracket, but rather a Ctrl+] character To enter the spe-cial character in vi’s insert mode, you would press Ctrl+v and then Ctrl+] The Ctrl+v command tells vi to insert the following key sequence as a Ctrl character sequence, with-out attempting to interpret it (Another example of this type of vi editing maneuver might
be to replace Ctrl+] with Enter, which would specify a carriage return sequence and be displayed as ^M.)
Sending the ^] special character causes the script to break out of the active telnet con-nection and drops you to the telnet’s interactive prompt At this point the script sends a quit command to the terminal server and the telnet session closes After the telnet port connection closes with the quit command, expect sends the user a final carriage return,
\n, to ensure that when the script finishes cleanly, the user will be back at her usual shell prompt