ftp

Short for File Transfer Protocol, it may refer to the protocol itself (RFC 959[L1 ]), the client application that is/was commonly used to communicate using that protocol, or the tcllib package that provides a native Tcl implementation of the client side.


Standard documentation of the package appears at http://tcllib.sourceforge.net/doc/ftp.html .

Related pages:


Here's an example implementation of a "deep" listing, which recurses into subdirectories:

    proc deep_list {host user pass dir} {
        set handle [::ftp::Open $host $user $pass]
            # An alternative would be to "::ftp::Cd ... $dir",
            #     then "::ftp::DeepList $handle .", then strip
            #     off the leading "./" (or equivalent).  That
            #     gives a slightly different format.  I'm not
            #     sure which has superior aesthetics.
        set result [::ftp::DeepList $handle $dir]
        ::ftp::Close $handle
        return $result
    }

    proc ::ftp::DeepList {handle directory} {
        set result {}
        set original [::ftp::Pwd $handle]
        foreach item [::ftp::NList $handle $directory/*] {
                # Why two clauses in the predicate?  Because,
                #    depending on the (run-time) value of 
                #    $::ftp::VERBOSE, we might receive either
                #    an exception, or a return value.  We need
                #    to handle either case gracefully.
            if {[catch {::ftp::Cd $handle $item} ret_val] || !$ret_val} {
                lappend result $item
            } else {
                ::ftp::Cd $handle $original
                set result [concat $result [::ftp::DeepList $handle $item]]
            }
        }
        return [lsort $result]
    }

Use this as

    set list_of_files [deep_list $host $user $pass $directory]

/* As a cut and paste code snippet it failed for me. The procedure deep_list_i is undefined. Art Morel [email protected] */

the previous proc was named deep_list_i just change that to ::ftp::DeepList and it should work


Commentary on the history of FTP specifications appear at http://www.wu-ftpd.org/rfc/ .


These days (21th century) system administrators have started shutting down FTP access for security reasons. A rather different protocol which could provide an alternative is fish.


Questions asked and answered

Stressing Switches with Multiple FTP Transfers

CJL (11 september 2007) - I'm putting together a little script to do multiple simultaneous ftp transfers (in both directions) as a way of stressing a switch, and I'm trying to use 'package require ftp' but am having problems with using it asynchronously. I want the bulk transfers to happen asynchronously (as a result of specifying '-command'), and simultaneously, making use of a callback via the '-progress' argument to keep track of what's happening. However, that makes setting up the connection awkward as any one call into the package, such as the initial '::ftp::Open', can cause multiple calls to the '-command' callback, meaning there doesn't seem to be any indication as to when the first command has completed and it's safe to proceed to the next command (i.e. '::ftp::Cd', '::ftp::Type' and then '::ftp::Get' in my case). I can find nothing in the docs to explain how to deal with this situation.

Lars H, quick suggestion: Have you considered adding an argument in the callbacks (a callback should allow a command prefix, not just a procedure name) which lets you keep track of what it was a callback for?

CJL - The callbacks already take a single argument to identify which server connection the particular call is associated with. Adding a 'reason' argument doesn't help identify when the 'reason' is complete. e.g. ::ftp::Open calls the callback three times, once each for 'user', 'password' and a non-fatal error about not being able to set the type (I don't remember the exact wording). For now, I've hardcoded an expected-number-of-responses value for each operation (Open, Cd, Filesize, etc...), but that will break in the event that the number of responses changes (e.g. if connecting to a different server makes the type setting error go away).

Lars H: So the problem is not with multiple connections as such, but with at all using the asynchronous mode of the ftp package. The documentation in this area is indeed insufficient (mentions there are keywords for the operations completed, but not which they are or what they mean -- could be reason to file a bug report against the documentation).

From looking at the implementation, it seems you're supposed to get a connect keyword when the connection is ready (either from the connect or connect_last branch of ::ftp::StateHandler), but if that doesn't happen then something seems strange. DEBUGging is probably in order to see what is going on in the connection to the server. Also, have you tried specifying -mode in the call to ftp::Open, since the default (which seems to be empty) produces an error?

CJL Yes, it's asynchronous mode that's causing me problems - I only mentioned the multiple simultaneous connections as justification for using async. '-mode' is supposed to default to 'active'. Explicitly setting it to either 'active' or 'passive' does not prevent the error (''error setting type""'). Stepping through the sequence of operations to initiate a transfer reveals that I do get a 'connect' response, but strangely, it's the second response to a 'cd' command (?!?!!?). Thanks for looking into this, but I'm afraid my cludge will have to do until I find more time to revisit the problem.

Lars H: Regarding -mode, I was thinking -type (ascii/binary), but there doesn't seem to be an ftp::Open option for this. Since it insists on setting the type, but doesn't have neither ascii nor binary as defeault, it seems it chooses "local mode" [L3 ] instead. Upon getting an error on this (which it ought to, since the TYPE command sent lacks a parameter!), it will not continue to report the connection completed, and indeed the state that is supposed to do this may sit in the queue until a cd command.

Conclusion: This is a bug in the ftp package. You might be able to work around it by doing

  set ::ftp::ftp${s}(Type) ascii

immediately after the ftp::Open. The $s is the serial number returned by ftp::Open.