[comp.lang.perl] sh can do it -- how about perl?

casterln@are.berkeley.edu (Gary Casterline) (05/29/90)

The following little script works fine.  It's part of the code
in "UNIX Relational Database Management", by Manis, Schaffer and
Jorgensen.  I'd like to do something similar in perl, but I'm
having trouble getting the right pipe(s) set up.

The idea is to use one program for both 'tables' and 'lists' by
formatting a 'listfile' temporarily as a 'tablefile', feeding this
data (in 'tableform') back into itself, and finally translating back
to the original list format.  I like the idea and its so short in sh:

	#!/bin/sh
	read HEAD
	if test -z "$HEAD"     # first line blank means listfile
	then
		(echo; cat) | listtotable | $0 $* | tabletolist
		exit 0
	fi
	[... further processing on 'table' using sh and awk not shown ...]

I must admit I'm learning a lot in my efforts to duplicate this,
but I'm sure there must be a cleaner way than the avenues I've 
explored so far.  Any ideas would be greatly appreciated.
----
Gary Casterline             .  ._ ._   Agricultural & Resource Economics
casterln@are.berkeley.edu  /-\ |< |-_  207 Giannini Hall
(415) 642-5583                         UC Berkeley, CA 94703
----

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (05/30/90)

In article <36587@ucbvax.BERKELEY.EDU> casterln@are.berkeley.edu (Gary Casterline) writes:
: The following little script works fine.  It's part of the code
: in "UNIX Relational Database Management", by Manis, Schaffer and
: Jorgensen.  I'd like to do something similar in perl, but I'm
: having trouble getting the right pipe(s) set up.
: 
: The idea is to use one program for both 'tables' and 'lists' by
: formatting a 'listfile' temporarily as a 'tablefile', feeding this
: data (in 'tableform') back into itself, and finally translating back
: to the original list format.  I like the idea and its so short in sh:
: 
: 	#!/bin/sh
: 	read HEAD
: 	if test -z "$HEAD"     # first line blank means listfile
: 	then
: 		(echo; cat) | listtotable | $0 $* | tabletolist
: 		exit 0
: 	fi
: 	[... further processing on 'table' using sh and awk not shown ...]

Hmm, echo with no argument is a no-op here.

: I must admit I'm learning a lot in my efforts to duplicate this,

Thanks, you're very diplomatic.   :-)

: but I'm sure there must be a cleaner way than the avenues I've 
: explored so far.  Any ideas would be greatly appreciated.

The difficulty you're having is with stdio buffering.  The most natural
way to write what you want in Perl might be something like

#!/usr/local/bin/perl

($in, $out) = <STDIN> eq "\n" ? ("listtotable|", "|tabletolist") : ("-" , ">-");

open(IN,  $in)  || die "Can't open $in: $!\n";
open(OUT, $out) || die "Can't open $out: $!\n";

while (<IN>) {
    s/foo/bar/;		# representative processing
    print OUT;
}
close IN;
close OUT;

Unfortunately, this doesn't work if stdio reads more than a single line
into the STDIN buffer, since listtotable will only pick up at the current
file position for fd 0.  One workaround, if you know it's coming from a
file, would be to say seek(IN, 0, 0) at an appropriate place.  If, however,
your input is coming down a pipe, I don't see an easy way to do it without
a temp file:

#!/usr/local/bin/perl

($in, $out) = <STDIN> eq "\n"
    ? ("listtotable </tmp/phooey$$ |", "|tabletolist")
    : ("-" , ">-");

open(TMP, ">/tmp/phooey$$");
print TMP <STDIN>;
close TMP;

open(IN,  $in)  || die "Can't open $in: $!\n";
open(OUT, $out) || die "Can't open $out: $!\n";

while (<IN>) {
    s/foo/bar/;		# representative processing
    print OUT;
}
close IN;
close OUT;

That's kindof yucky.  One possible workaround would be to write a physread
subroutine that emulates what the shell does by reading a character at
a time from fd 0 on un-lseek()able input.  This only works if you have
syscall().  The read call that Perl gives you unfortunately calls fread(),
which won't have the same effect.

do 'syscall.h' || die "Did you makelib syscall.h?\n";

sub physread {
    local($line);
    if (($pos = syscall(&SYS_lseek, fileno(STDIN), 0, 1)) == -1) {
	local($buf) = ' ';
	while (syscall(&SYS_read, fileno(STDIN), $buf, 1) == 1) {
	    $line .= $buf;
	    last if $buf eq "\n";
	}
    }
    else {
	$line = <STDIN>;
	seek(STDIN, $pos + length($line), 0);
    }
    $line;
}

Or something like that.  It's always a bit dangerous bypassing stdio.

Alternatively, you write listtotable and tabletolist as Perl subroutines.

This is just one of those places where we pay the price for the efficiency
that stdio normally provides.  I suppose I could arrange things so that
setting $| on STDIN would make it do unbuffered input.  Things could get
strange, however...

Larry