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

I'm by no means a great perl programmer, although I've improved every year thus far. I had a quick and dirty code competition with some friends to write the shortest (legible) perl version of the tcsh 'watch' command. I won with the following, but I'm sure it can be shortened further. I'd like to know how (and preferably with it kept at least semi-legible).

#!/usr/bin/perl -w use strict; my (%users, @in); while (<DATA>) { chomp; $users{$_} = 0; } for (;;) { @in = split/ /,`users`; foreach my $n (keys %users) { if ( !$users{$n} && map {/^$n$/} @in ) { $users{$n} = 1; print scalar localtime, " $n has logged in \n"; } if ( $users{$n} && ! map {/^$n$/} @in ) { $users{$n} = 0; print scalar localtime, " $n has logged out \n"; } } sleep 2; } __DATA__ list of users to watch

20040812 Edit by ysth: change pre tags to code tags

Replies are listed 'Best First'.
Re: Shorten this code
by ccn (Vicar) on Aug 12, 2004 at 21:19 UTC

    #!/usr/bin/perl -w0777 @users{split /\s+/, <DATA>}=(); while(sleep 2) { for my $n (keys %users) { $_ = `users`; $state = ''; $users{$n} = 1, $state = 'in' if !$users{$n} && /\b$n\b/; $users{$n} = 0, $state = 'out' if $users{$n} && !/\b$n\b/; print localtime() . " $n has logged $state\n" if $state; } }
      Let's get rid of the $state variable.
      #!/usr/bin/perl -l -w0777 @users{split /\s+/, <DATA>}=(); while(sleep 2) { $_ = `users`; for my $n (keys %users) { if ($users{$n} xor /\b$n\b/) { $users{$n} = /\b$n\b/; print localtime() . " $n has logged ", $users{$n} ? "in" : "out" +; } } }
      UPDATE: There are a number of golf tricks that I've left out. For instance ("out", "in")[$users{$n} = /\b$n\b/] manages to both remember and select the current state, removing one line. But readability suffers.

        Thanks everyone for your contributions. I have only one main question:

        #!/usr/bin/perl -l -w0777

        Whats the 0777 do? And has it anything to do with not using strict?

Re: Shorten this code
by blokhead (Monsignor) on Aug 12, 2004 at 21:01 UTC
    I'd combine the if-statements somehow:
    my %users = map { $u => 0 } qw/ list of users to watch /; while (1) { my %curr = map { $_ => 1 } split / /, `users`; for (keys %users) { if ( $users{$_} != $curr{$_} ) { $users{$_} = $curr{$_}; printf "%s: $_ has logged %s\n", scalar localtime, ($users{$_} ? "in" : "out"); } } sleep 2; }
    But you know, it's much more fun to do something completely off the wall:
    use Tie::Scalar; @ISA = (Tie::StdScalar); sub TIESCALAR { bless { k => $_[1], v => $_[2] }, $_[0] }; sub FETCH { $_[0]{v} } sub STORE { $_[0]{v} != $_[1] and printf "%s: $_[0]{k} logged %s\n", scalar localtime, ($_[1] ? "in" : "out"); $_[0]{v} = $_[1]; } my (@list, %user) = qw/ list of users to watch/; tie $user{$_}, main => $_, undef for @list; while (1) { my %curr = map { $_ => 1 } split / /, `users`; $user{$_} = $curr{$_} for @list; sleep 2; }
    Hmm.. then again, you did say "readable", didn't you? ;)

    blokhead

Re: Shorten this code
by BrowserUk (Patriarch) on Aug 12, 2004 at 21:44 UTC

    #!/usr/bin/perl -w use strict; $| = 1; my %users; $users{ $_ } = 0 for split "\n", do{ local $/; <DATA> }; { my %in; @in{ split / /, `users` } = (); for( keys %users ) { my $change = exists $in{$_} && !$users{$_} ? do{ $users{$_}++; 'in' } +: !exists $in{$_} && $users{$_} ? do{ $users{$_}--; 'out' } +: (); $change and print localtime() . " $_ has logged $change\n"; } sleep 2; redo; } __DATA__ Fred Wilma Barney Betty

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: Shorten this code
by Aristotle (Chancellor) on Aug 13, 2004 at 02:52 UTC

    Not the shortest, but see for yourself.

    #!/usr/bin/perl -w use strict; use Set::Scalar; chomp( my @l = <DATA> ); my %is_watched; @is_watched{ @l } = ( 1 ) x @l; my $prev = Set::Scalar->new(); { my $cur = Set::Scalar->new( grep $is_watched{ $_ }, split " ", `us +ers` ); my $t = localtime; print( map( "$t $_ has logged in\n", ( $cur - $prev )->members ), map( "$t $_ has logged out\n", ( $prev - $cur )->members ), ); $prev = $cur; sleep 2; redo; }

    Makeshifts last the longest.