[Ssh-sftp-perl-users] Using Expect.pm to drive sftp binary (example)
Brought to you by:
dbrobins
From: Mark F. <mar...@ea...> - 2004-11-24 02:15:53
|
Seth, Below is the example of using Expect.pm to interact with an sftp executable. I hope it's not too much of a mess after all the email line-wrapping. I carved this out of a larger script where many of these functions are in reuseable subroutines (so I could use the same code for sftp, or ftp, or anything else). You'll probably hit a few errors since this isn't the exact code I execute. But, generally, the below is what I do. Notwithstanding the syntax errors you may receive, it should be all you need to do to use Expect.pm. If you can improve this example and post something better, please do. (Also, this isn't meant to imply the ssh-sftp Perl module isn't useful. The expect-driven binary is just another way that might be helpful.) Mark ========================================= #! /usr/bin/perl -w use strict; use Expect; #*************************************************************************** **** # This is an example of using Expect.pm (search CPAN for it. Current version is # 1.15 on Nov. 23 2004. Or, go to http://sourceforge.net/projects/expectperl/ . # The "Io_Tty" module is also shown at that site. I don't recall if the # installation requires that module.) # # The following should recreate this command line execution of sftp: # # sftp -oidentityFile=/home/server_user/.ssh/id_dsa.nopass -oUser=client_user some_host <<EOF # put source_file target_file # EOF # # Just change the hardcoded @expectParms to match your requirements (user, host # identity filename). # # WARNING. I carved the following out of a larger script with more abstract # uses of what follows. I haven't *actually* executed the following. You may # run into typos and other problems that cause the Perl interpreter to fail. # It's only meant to help you start in the right direction without the need to # read the Expect.pm documentation and experiment with it. The following should # work after you do my debugging for me. :) #*************************************************************************** **** #--------------------------------------------------------------------------- ---- # Create the object and set the logging to go to a subroutine where log messages # can be accumulated and later interrogated between subcommands (to navigate and # selectively execute subcommands). #--------------------------------------------------------------------------- ---- my $expect = new Expect; our $expect_log; # used by the subroutine coderef'ed on the next line. $expect->log_file(\&logExpect); #--------------------------------------- # Uncomment the following for debugging # information. #--------------------------------------- #$expect->exp_internal(1); #$expect->debug(2); #--------------------------------------------------------------------------- ---- # Hardcoded command-line parms to sftp. You'll want to change this to whatever # command line you use. #--------------------------------------------------------------------------- ---- my @expectParms; $expectParms[0] = '-oidentityFile=/home/server_user/.ssh/id_dsa.nopass'; $expectParms[1] = '-oUser='client_user'; $expectParms[2] = 'some_hostname'; #--------------------------------------------------------------------------- ---- # Start the command. This is where it starts. The parms are passed as an array. #--------------------------------------------------------------------------- ---- if (!$expect->spawn('sftp', @expectParms )) { print 'Command failed to exectute.' . "\n"; exit 99; } # end failed spawn #--------------------------------------------------------------------------- ---- # Setup the expected results. They can be literal strings (prefixed with -ex), # or regular expressions (prefixed with -re). You have to follow the pattern # below, even if they are all literals or regexes. The prefix is one array # element, the value the next array element. Over and over and over, prefix and # value in alternating array elements. # # The ->expect method call will tell you which element matched. #--------------------------------------------------------------------------- ---- my @expectedResults; $expectedResults[0] = '-ex'; $expectedResults[1] = 'sftp>'; $expectedResults[2] = '-re'; $expectedResults[3] = '^sftp\>\n'; # sftp prompt and newline (as an example. You # may not get a newline after the sftp # prompt.) #--------------------------------------------------------------------------- ---- # Now do the expect, for the specified timeout, looking for the values in the # prefix/value alternating array created above. # # Note that @expect_results is a differnt array. It contains # # [0] The item in @expectResults which matched. # [1] The error code (undef, 1:TIMEOUT, 2: EOF which means connection closed I # think, 3:spawn id($fileno) died, 4:$! indicating whatever error was set in # $ERRNO during the last read on $object's handle) # [2] The string that successully matched. # [3] The string preceeding the match string. # [4] The string following the match string. # # You'll be most interested in whether the error is undef, and which element of # @expectedResults was matched (to make decisions about what to do next). # # Next you'll be interested in the $expect_log variable which was accumulated # with all the result messages. If this was a directory listing, you'd want to # expect for the sftp> prompt to know it received the entire directory listing. # You might also be interested in any strings indicating the directory wasn't # found (indicating an error). Then you'd be interested in the error_log # variable to interrogate the actual directory listing data (to look for a # specific file, perhaps). #--------------------------------------------------------------------------- ---- my @expect_results; @expect_results = $expect->expect(30, @expectedResults); if ($expect_results[1]) { if ($expect_results[1] ne '1:TIMEOUT') { print 'Got a non-timeout error." . "\n"; exit 99; } else { print 'Timed out.' . "\n"; exit 99; } print 'Matched on expected value #: ' . $expect_results[1] . "\n"; #--------------------------------------------------------------------------- ---- # Clear the accumulater. This is the storage each ->expect method call will # search. Clear it before doing another ->expect (unless you have a good reason # not to). #--------------------------------------------------------------------------- ---- $expect->clear_accum(); #--------------------------------------------------------------------------- ---- # Send a subcommand. This doesn't seem to return a success of failure. You just # send the subcommand and let the ->expect method call do the work for you. #--------------------------------------------------------------------------- ---- $expect->send('put source_file target_file' . "\n"); #--------------------------------------------------------------------------- ---- # Perform the ->expect method like above. I won't recreate it here. But, you # want to setup the the array of expected literal(s) and/or regex(es). Call # the ->expect method, and determine how the method returned to you (error, # timeout, which element it matched on). Perhaps process the $expect_log # variable if you want to have greater control over the results of the # subcommand. #--------------------------------------------------------------------------- ---- # The POD has a lot more options. The above seems to work well for sftp. You can # do the ->send and ->expect methods repeatedly until you ->send('quit') and # probably don't care about the result. exit; #*************************************************************************** **** # logExpect # # Used by the expect->log_file method as a callback to capture result messages. #*************************************************************************** **** sub logExpect { foreach (@_) { $expect_log .= $_; } return; } |