Writing and reading to pipes (Perl)

When writing Perl scripts to automate shell tasks, file handlers will be used for reading files, writing to files, executing commands, redirecting output, … But these handlers can only be opened in one direction if they are created with the standard Perl subroutine open.

In some cases it is actually required to create a two way communication with the file handler, for instance when creating a pipe. In a pipe, some information is written to the pipe (left side of the pipe) which is read and analyzed by the right side of the pipe. This right of the pipe, is then able to produce some output.
If it is required to catch that output, we will need to have two file handlers!

A core module called IPC::Open2 provides an open2 subroutine, which allows to create a read and write file handler.

In the following example, we will mimic the shell command cat file|file -rik -, without calling the cat command. Instead we will open the file in RAW mode using Perl and then use file handlers to feed it to the file command and afterwards read the output.

#!/usr/bin/perl
use strict; use warnings;
use IPC::Open2;

my @files = (qw{
        parse_svn_log.zip scan_new.xsd bla.pl
        ../../root.tgz ../../bla.tar ../../Toyota.pdf
        ../../sms.bak scan.xml
        });

foreach my $file (@files) {
    print("Scanning $filen");
    open my $fh, '<:raw', $file or die "schijt: $!n";
    my $fc = <$fh>;
    close ($fh);
    my $mime = _get_mime_type($fc);

    print("- file: $file | mime: $mimen") if defined $mime;
}

sub _get_mime_type {
    my($data) = @_;
    my $mime = do { open2(my $read, my $write, 'file -rik -');
                    print {$write} $data."n"; 
                    close($write);
                    local $/;
                    <$read>; 
    }; 

    chomp($mime);
    if(defined $mime and $mime !~ m/^s*$/) {
        # 1) remove the '/dev/stdin:' part
        # 2) MIME type are always in the format: blabla/blabla
        # 3) in some rare cases their is a semicolon at the end
        #    e.g.: /dev/stdin: application/x-symlink; charset=binary
        $mime =~ s/.*?://s;
        ($mime) = ($mime =~ /(S+?/S+)/s);
        $mime =~ s/;.*$//g;    
    }
    else {
        $mime   = 'text/plain';
    }
    return $mime;
}

Output produced by script:

Scanning parse_svn_log.zip
- file: parse_svn_log.zip | mime: application/zip
Scanning scan_new.xsd
- file: scan_new.xsd | mime: application/xml
Scanning bla.pl
- file: bla.pl | mime: text/x-perl
Scanning ../../root.tgz
- file: ../../root.tgz | mime: application/x-gzip
Scanning ../../bla.tar
schijt: No such file or directory

Comments

Writing and reading to pipes (Perl) — 1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.