#!/usr/bin/perl -w # Copyright 2002 Ed Avis. See the file COPYING. =head1 NAME pip - wrap programs to use them as filters =head1 SYNOPSIS C where for every C<-i> (input), C<-o> (output), or C<-b> (both) switch, there is one argument of the form '-', or '-.foo' for some string 'foo'. =head1 DESCRIPTION The '-' or '-.foo' arguments are placeholders and are matched up left-to-right with the C<-i>, C<-o> and C<-b> switches. Each placeholder is replaced by the name of a temporary file. For a C<-i> switch this is an input file, which is created by pip before the command runs and contains data read from pipE<39>s standard input. For C<-o> it is an output file, whose contents are printed after the command runs. For C<-b> the temporary file is both an input file and an output file. If a placeholder argument has the form '-.foo' then the temporary filename will end in '.foo'. This is useful for programs which change behaviour based on filename, such as the C compiler. But note that an argument of '-foo' would not be changed. The C<-I> switch is used with programs that can read from standard input, but require it to be seekable. Pip buffers standard input into a file if necessary. (If both C<-I> and input placeholders are used, the programE<39>s standard input will be buffered but unchanged - not empty as it would be without C<-I>.) Similarly <-O> causes the programE<39>s standard output to be buffered to a file and written after the program has terminated. If there are multiple input files then each input file gets the same data. If there are multiple output files then each output file is printed in turn. If there are no input files then standard input is passed through unchanged to the program. If the program prints anything to its standard output then this appears before the content of any output files (whether or not the C<-O> flag was used). =head1 EXAMPLES =over =item C will read a file from standard input, and give it to mozilla to display. The final commandline might be C. =item C will pipe a C program through the compiler, giving an executable on standard output. The temporary input filename given to cc will end in '.c'. =item C will read data, give the user the chance to modify it in emacs, and then print it. =item C runs dvips in 'filter' mode, which can read from stdin but needs it to be seekable. =item C turns pnmtotiff, which requires that its stdout be seekable, into a general filter program. =back =head1 COMPARISON WITH REAL PIPES When using pip you must wait for all input to be consumed before the command is run, and for the command to exit before seeing any of the output. You donE<39>t get partial output as you would with pipes. But pip works with programs that seek backwards and forwards in their input, and which may write their output in a strange order too. This makes it work in places where the shellE<39>s process substitution would not. @BEGIN_DEV_STDIN For some programs you may find the devices /dev/stdin and /dev/stdout a better option than pip, since these allow partial input and partial output. However they are not seekable, so will not work with all programs. @END_DEV_STDIN =head1 BUGS Because pip unlinks temporary files before exiting, and exits as soon as running the program returns, any command which puts itself into the background before opening its input files will not work. Not related to the CP/M command of the same name. =head1 SEE ALSO L. =head1 AUTHOR Ed Avis, ed@membled.com =cut use strict; use IO::Handle; use File::Temp qw(tempdir); sub tmpnam(); my $VERSION = '1.2'; # Whether to remove the temporary directory after running. Normally # on, but turning it off may be useful for debugging. # my $CLEANUP = 1; sub usage() { print STDERR <. END ; } if (@ARGV < 1) { usage(); exit(1); } # Split the arguments into flags, and the rest. my $flags = ''; my @rest = (); # Get the flags into a big lump. while (my $arg = shift @ARGV) { if (index('--help', $arg) == 0) { usage(); exit(0); } elsif (index('--version', $arg) == 0) { print STDERR <$_") or die "can't write to $_: $!"; push @handles, $fh; } while () { my $handle; foreach $handle (@handles) { print $handle $_; } } foreach (@handles) { close $_; } } # Run the program, with stdin and stdout redirected if appropriate. local *OLDOUT; if (defined $stdin_file) { # Won't need to restore stdin afterwards. open(STDIN, $stdin_file) or die "cannot reopen $stdin_file: $!"; } if (defined $stdout_file) { open(OLDOUT, '>&STDOUT') or die "cannot dup stdout: $!"; open(STDOUT, ">$stdout_file") or die "cannot reopen $stdout_file for writing: $!"; } system($prog, @args); if (defined $stdout_file) { open(STDOUT, '>&OLDOUT') or die "cannot dup stdout back again: $!"; } if ($CLEANUP) { # Remove input files. foreach (@infiles) { next if $is_outfile{$_}; (not -e $_) or unlink or die "cannot unlink $_: $!"; } } # Print output if necessary, and remove files. my $outfile; foreach $outfile (@outfiles) { unless (open (OUTFILE, $outfile)) { if ($! =~ /^No such file or directory/ and $outfile !~ m!\.[^/]*$!) { # Sometimes DOSish programs add an extension to the output # filename without being asked. Sniff around and see if # we can find any evidence of this. # if (-e $outfile) { die "open() said $outfile doesn't exist, but it does"; } my @poss = <$outfile.*>; if (@poss == 0) { # Nope, nothing. die "$prog didn't create $outfile or any $outfile.*\n"; } elsif (@poss == 1) { # It looks like the program has indeed created an # output file with a silly name. # my $o = $poss[0]; $o =~ /^$outfile(\..*)$/ or die; my $ext = $1; warn <) { print; } close OUTFILE; if ($CLEANUP) { unlink $outfile or die "cannot unlink $outfile: $!"; } } # tmpnam() # # Return a name for a temporary file. I believe this is secure # because it creates a 'private' directory and then uses that. Cannot # use POSIX's tmpnam() because I want to ensure 8.3 filenames (and # anyway, this ought to be more secure). # my $tempdir; my $num; sub tmpnam() { die 'usage: tmpnam()' if @_; for ($tempdir) { if (not defined) { $_ = tempdir('XXXXXXXX', CLEANUP => $CLEANUP, TMPDIR => 1); die 'cannot make temporary directory' if not defined; die "failed to make directory $_" if not -d; } } # Pick an unused filename in this directory. Since we created the # dir ourselves we don't need to look at what it contains. # $num = 0 if not defined $num; return "$tempdir/" . $num++; }