| Expect for Windows® User Guide |

# Tutorial: Remote Directory Lister

- Introduction
- Including the Expect Package and Setting Variables
- Displaying Command-Line Help
- Parsing Command-Line Arguments
- Spawning the Session
- Handling Errors
- Sending the Request
- Closing the Spawned Session

## Introduction

This tutorial shows how the Expect package can be used within a Tcl command-line script to log on to a remote machine via Telnet or SSH and return the contents of the current working directory.

Note: To run this script, you must have ActiveState Expect for Windows and ActiveTcl 8.4 or later installed on your machine. You also require remote access to a Unix machine that is running Telnet or SSH.

First, run the Tcl script "remotels.tcl" to see how it operates. Then progress through the rest of the tutorial, which examines essential portions of the code and explains how Expect is incorporated.

The command-line syntax required to run "remotels.tcl" varies depending on whether you are accessing the remote machine via Telnet or SSH.

For example, if you are using Telnet (the default `-login` option), and are running "remotels.tcl" from its default location, enter the following at the command prompt.

> `C:\Tcl\bin\remotels.tcl -host hostname -user username`

If, instead, you are accessing the remote host via SSH, you must also specify "ssh" as the login type using the `-login` option.

In both cases, the program prompts for a password.

When the script runs, it returns the number of files and directories in the current working directory, along with a list of their names.

For a demonstration of a similar, GUI-based script, see "tkremotels.tcl" in the Demos section of the ActiveState Expect for Windows User Guide.

Top

## Including the Expect Package and Setting Variables

The package require Expect statement makes the Expect package available to the "remotels.tcl" application. It should appear along with other necessary package commands near the beginning of any Tcl script that uses Expect. Since a version is not specified, the highest available version of Expect is loaded.

A number of variables are set, including exp::winnt_debug, which Expect uses to enable viewing of a controlled console. If this variable is not set, the console remains hidden.

The array set command initializes the variables that are passed through the script so that the local machine can access a directory on a remote machine. Note that the ls variable is defined as "bin/ls/ -A1". This runs Unix's `ls` command on the remote machine and specifies how the information is to be displayed.

>     package require Expect

>     exp_log_user 0

>     set exp::nt_debug 1

>     set timeout 10

>     set env(TERM) dumb

>     array set OPTS {
>         host    ""
>         user    ""
>         passwd  ""
>         login   telnet
>         prompt  "(%|#|>|\\$) $"
>         ls      "/bin/ls -A1"
>     }

Top

## Displaying Command-Line Help

The `proc usage` procedure establishes the command-line options for "remotels.tcl". If the script is run without any options, a list of options and their definitions are displayed on the command line.

>     proc usage {code} {
>         global OPTS
>         puts [expr {$code ? "stderr" : "stdout"}] \
>         "$::argv0 -user username -host hostname ?options?
>         -passwd password (you will be prompted if none is given)
>         -login  type     (telnet, ssh, rlogin, slogin {$OPTS(login)})
>         -prompt prompt   (RE of prompt to expect on host {$OPTS(prompt)})
>         -log    bool     (display expect log info {[exp_log_user]})
>         -ls     lspath   (path to ls on host {$OPTS(ls)})
>         -help            (print out this message)"
>         exit $code
>     }

Top

## Parsing Command-Line Arguments

The first part of this section uses `proc parseargs` to specify which patterns must be exactly matched for the command-line arguments to be parsed. Another significant code block in this section involves the `stty -echo` command. This command disables echoing. When a user types a password at the command prompt, the terminal mode is altered so that typed characters are hidden. Once the password is read with the gets command, the `stty echo` command re-enables echoing.

>     proc parseargs {argc argv} {
>         global OPTS
>         foreach {key val} $argv {
>         switch -exact -- $key {
>             "-user"   { set OPTS(user)   $val }
>             "-host"   { set OPTS(host)   $val }
>             "-passwd" { set OPTS(passwd) $val }
>             "-login"  { set OPTS(login)  $val }
>             "-prompt" { set OPTS(prompt) $val }
>             "-ls"     { set OPTS(ls)     $val }
>             "-log"    { exp_log_user $val }
>             "-help"   { usage 0 }
>         }
>         }
>     }
>     parseargs $argc $argv

>     if {$OPTS(host) == "" || $OPTS(user) == ""} {
>         usage 1
>     }

>     if {$OPTS(passwd) == ""} {
>         stty -echo;
>         puts -nonewline "password required for $OPTS(host): "
>         flush stdout
>         gets stdin ::OPTS(passwd)
>         stty echo
>     }

>     proc timedout {{msg {none}}} {
>         send_user "Timed out (reason: $msg)\n"
>         if {[info exists ::expect_out]} {
>         parray ::expect_out
>         }
>         exit 1
>     }

Note: Although the `stty` and `send_user` commands shown above are Expect commands, they do not include the `exp` prefix. This is to demonstrate that, while it is considered best practice to use the prefix, commands without the prefix are equally valid. The exception is when you are using another Tcl extension (such as Tk) that includes a command with the same name. If a collision occurs, the Expect command is likely to be overridden by the command in another extension.

Top

## Spawning the Session

If you are logging on to the remote machine using "ssh", "slogin" or "rlogin", the information gets processed in a slightly different manner. With any of these methods, it is necessary to include an additional `-l` option to specify a username.

Next, the $spawn_id variable is captured, storing information about this spawn session in memory for future reference.

If you are logging in via Telnet, the final code block in this section is required to pass the username to Telnet. If the login is completed before the script times out, the exp_send command passes the username.

>     switch -exact $OPTS(login) {
>         "telnet" { set pid [spawn telnet $OPTS(host)] }
>         "ssh"    -
>         "slogin" -
>         "rlogin" { set pid [spawn $OPTS(login) $OPTS(host) -l $OPTS(user)] }
>     }

>     set id $spawn_id

>     if {$OPTS(login) == "telnet"} {
>         expect -i $id timeout {
>         timedout "in user login"
>         } eof {
>         timedout "spawn failed with eof on login"
>         } -re "(login|Username):.*" {
>         exp_send -i $id -- "$OPTS(user)\r"
>         }
>     }

Top

## Handling Errors

The error-handling section of the script is a `while` loop that anticipates a number of problems that could occur during login. This section is not exhaustive. For example, you could also add provisions for invalid usernames and passwords.

If the login is not completed during the allotted 10-second time frame, which is set at the beginning of "remotels.tcl" (`set timeout 10`) and specified with `expect -i $id timeout`, the program displays an appropriate error message.

The remainder of this loop makes use of the exp_send command to allow for other scenarios, such as the user typing "yes" when prompted to proceed with the connection, entering a password, or resetting the terminal mode.

>     set logged_in 0
>     while {!$logged_in} {
>         expect -i $id timeout {
>         timedout "in while loop"
>         break
>         } eof {
>         timedout "spawn failed with eof"
>         break
>         } "Are you sure you want to continue connecting (yes/no)? " {
>         exp_send -i $id -- "yes\r"
>         } "\[Pp\]assword*" {
>         exp_send -i $id -- "$OPTS(passwd)\r"
>         } "TERM = (*) " {
>         exp_send -i $id -- "$env(TERM)\r"
>         } -re $OPTS(prompt) {
>         set logged_in 1
>         }
>     }

Top

## Sending the Request

If the login is successful, the code in the `if` statement below is used to send the "ls" request to display files and directories. After the request is sent with exp_send, the resulting output is captured in the `dir` variable, which is set on the fourth line of the code shown below.

>     if {$logged_in} {
>         exp_send -i $id -- "$OPTS(ls)\r"
>         expect -i $id timeout {timedout "on prompt"} -re $OPTS(prompt) {
>         set dir $expect_out(buffer)
>         }
>         exp_send -i $id -- "exit\r"
>         if {[info exists dir]} {
>         regsub "\r" $dir "" dir
>         set files [split $dir \n]
>         set files [lrange $files 1 [expr {[llength $files]-2}]]
>         puts "\n[llength $files] FILES AND DIRS:"
>         puts $files
>         }
>     }

Top

## Closing the Spawned Session

The exp_close command ends the session spawned by "remotels.tcl". Just to be sure that session does indeed close, the exp_wait command causes the script to continue running until a result is obtained from the system processes. If the system hangs, it is likely because `exp_close` was not able to close the spawned process, and you may need to kill it manually.

>     exp_close -i $id

>     exp_wait -i $id