blindluke has asked for the wisdom of the Perl Monks concerning the following question:

Enlightened Monks!

I'm trying to log user sessions with a program that does not support activity logging. I've decided to write a wrapper - some simple shell, that would pass a given line of input to this program, and at the same time log this line to a file. My initial code looks like this:

#!/usr/bin/perl -w use strict; use Term::ReadLine; use POSIX qw(strftime); my $username = getlogin(); my %cfg = ( shell => '/bin/bash', dateformat => '[%Y%m%d-%H%M%S] ', logpath => '/tmp/sessionlogs/', logname => "$username.session.log", ); my @prohibited_cmds = ( 'mc', '/usr/bin/mc', ); open LOGFILE, ">>$cfg{logpath}$cfg{logname}" or die "Cannot open log f +ile!"; print LOGFILE "-----------------------------------\n"; print LOGFILE strftime $cfg{dateformat}, localtime; print LOGFILE "Session started for $username.\n"; my $term = new Term::ReadLine 'Shellwrapper'; $SIG{'INT'} = 'IGNORE'; while (defined ($_ = $term->readline('SH+log> '))) { if ((/^\s*quit\s*$/) or (/^\s*exit\s*$/)) { print LOGFILE strftime $cfg{dateformat}, localtime; print LOGFILE "$_\n"; last; } # first log timestamp and commandline print LOGFILE strftime $cfg{dateformat}, localtime; print LOGFILE "$_\n"; # then execute system "$cfg{shell} -c '$_'"; print "\n"; } print LOGFILE strftime $cfg{dateformat}, localtime; print LOGFILE "Session terminated for $username.\n"; close LOGFILE;

I've substituted the program with bash, and simplified the code a bit, but this is roughly it. And it seems to work, too. The logfile looks like this:

----------------------------------- [20120516-222413] Session started for testuser17. [20120516-222419] test shellexample [20120516-222420] exit [20120516-222420] Session terminated for testuser17.

The problem is, that this works only a line at a time. And all tasks that require a few related lines of input - like set a variable, set another variable, and then run a command - well, they won't work.

So, I'm starting to think: maybe I should run the wrapper shell, run the program, and then establish some communication between the two. Now, I'm looking at IPC::Run3, but all the examples that I have found are using run3 once, with a fixed STDIN, and I'm looking for a scenario, when I'm gathering input, and passing it to the program continuously.

So, here are my questions (thanks for the patience): am I looking at the right tool for the job, and can it be done with IPC::Run3? If yes, could you provide some example? If not, what should I use? What problems should I be aware of?

Regards,
Luke Jefferson

Replies are listed 'Best First'.
Re: Logging user input to a file, and passing it to an external program, at the same time
by sauoq (Abbot) on May 16, 2012 at 21:58 UTC

    See Expect.

    Update: Seriously, just use Expect. It's trivial...

    use Expect; my $expect = Expect->spawn('/bin/bash'); $expect->log_file("./my.log"); $expect->interact();

    You can give log_file() a code ref if you want to intercept the lines. That's how you might add time stamps, for example, but you'll have to write the lines out to your log too in that case.

    -sauoq
    "My two cents aren't worth a dime.";
Re: Logging user input to a file, and passing it to an external program, at the same time
by Khen1950fx (Canon) on May 16, 2012 at 22:20 UTC
    It worked for me; however, it wouldn't open LOGFILE on the first try, so I made a few minor adjustments.
    #!/usr/bin/perl -l BEGIN { $| = 1; } use strict; use warnings; use Term::ReadLine; use POSIX qw(strftime); my $username = getlogin; my $logpath = '/tmp/sessionlogs'; my(%cfg) = ( shell => '/bin/bash', dateformat => '[%Y%m%d-%H%M%S] ', logpath => $logpath, logname => "$username.session.log", ); my(@prohibited_cmds) = ( 'mc', '/usr/bin/mc', ); open LOGFILE, ">>$cfg{'logpath'}$cfg{'logname'}" or die "Cannot open log file: $!"; print "enter 'exit' to quit."; print LOGFILE "-----------------------------------\n"; print LOGFILE strftime( $cfg{'dateformat'}, localtime ); print LOGFILE "Session started for $username."; my $term = Term::ReadLine->new('Shellwrapper'); $SIG{'INT'} = 'IGNORE'; while (defined ($_ = $term->readline('SH+log> '))) { if (/^\s*quit\s*$/ or /^\s*exit\s*$/) { print LOGFILE strftime( $cfg{'dateformat'}, localtime ); print LOGFILE "$_\n"; last; } print LOGFILE strftime( $cfg{'dateformat'}, localtime ); print LOGFILE "$_\n"; system "$cfg{'shell'} -c '$_'"; print "\n"; } print LOGFILE strftime( $cfg{'dateformat'}, localtime ); print LOGFILE "Session terminated for $username."; close LOGFILE;