如何从perl中的stdin和文件中解压缩透明gzip?

时间:2017-06-10 23:14:28

标签: fasta fastq compression perl

我已经编写了一些用于处理FASTA / FASTQ文件的脚本(例如fastx-length.pl),但是希望使它们更通用,并同时接受压缩和未压缩文件作为命令行参数和标准输入(以便脚本&#34;只是工作&#34;当你向它们扔掉随机文件时)。对于我来说,在未压缩和压缩文件(例如压缩读取文件,未压缩的组合基因组)上工作是很常见的,而<(zcat file.fastq.gz)之类的插件很快就会烦人。

这是fastx-length.pl脚本中的一个示例块:

...
my @lengths = ();
my $inQual = 0; # false
my $seqID = "";
my $qualID = "";
my $seq = "";
my $qual = "";
while(<>){
  chomp; chomp; # double chomp for Windows CR/LF on Linux machines
  if(!$inQual){
    if(/^(>|@)((.+?)( .*?\s*)?)$/){
      my $newSeqID = $2;
      my $newShortID = $3;
      if($seqID){
        printf("%d %s\n", length($seq), $seqID);
        push(@lengths, length($seq));
      }
...

我可以看到IO::Uncompress::Gunzip通过以下方式支持透明解压缩:

  

如果设置了此选项且输入文件/缓冲区不是压缩数据,则模块将允许读取它。

     

此外,如果输入文件/缓冲区确实包含压缩数据并且紧随其后有非压缩数据,则设置此选项将使该模块将整个文件/缓冲区视为单个数据流。

我想基本上将透明解压缩插入the diamond operator,在加载每个文件和从文件输入中读取一行之间。有谁知道我怎么能这样做?

3 个答案:

答案 0 :(得分:5)

我经常使用:

die("Usage: prog.pl [file [...]]\n") if @ARGV == 0 && -t STDIN;
push(@ARGV, "-") unless @ARGV;
for my $fn (@ARGV) {
    open(FH, $fn =~ /\.gz$/? "gzip -dc $fn |" : $fn =~ /\.bz2$/? "bzip2 -dc $fn |" : $fn) || die;
    print while (<FH>);
    close(FH);
}

此策略仅适用于gzip等具有适当文件扩展名的文件和名称文件,但一旦满足这些要求,它就可以同时处理各种文件类型。至于-t STDIN,请参阅explanation here

答案 1 :(得分:2)

这也是我长期想做的事情。直到最近我才学会了如何做到这一点。

该方法不需要任何文件命名约定。相反,它会检查the gzip magic number0x1f8b。它需要读取每个文件的前两个字节作为二进制流(使用一个名为unpack的非常漂亮的函数),并检查字节是否与gzip的幻数相匹配。这似乎对我有用:

$ echo "hi world" | gzip -c > hi_world.gz
$ echo "hi world" > hi_world.txt
$ echo "hi world" | gzip -c > not_a_gz_file
$ perl testgz.pl hi_world.gz hi_world.txt not_a_gz_file
hi_world.gz is gzipped!
hi_world.txt is not gzipped :(
not_a_gz_file is gzipped!

testgz.pl的内容如下。请原谅我的perl。已经有一段时间......

# testgz.pl
my $GZIP_MAGIC_NUMBER = "1f8b";
my $GZIP_MAGIC_NUMBER_LENGTH = 2; # in bytes

for my $arg (@ARGV){
    if(is_gzipped($arg)){
        print "$arg is gzipped!\n";
    } else{
        print "$arg is not gzipped :(\n";
    }
}


sub is_gzipped{
    my $file_name = shift;
    open(my $fh, "<", $file_name)
      or die "Can't open < $file_name: $!";
    read($fh, $line, $GZIP_MAGIC_NUMBER_LENGTH);
    close($fh);
    return is_line_gzipped($line);
}

sub is_line_gzipped{
    my $line = shift;
    my $is_gzipped = 0;
    if (length($line) >= $GZIP_MAGIC_NUMBER_LENGTH){
        my $magic_number = unpack("H4", $line);
        $is_gzipped = 1 if($magic_number == $GZIP_MAGIC_NUMBER);
    }
    return $is_gzipped
}

在回答问题时,我建议您使用函数is_gzipped检查要打开的文件,然后根据结果选择方法。

答案 2 :(得分:0)

我认为我最挣扎的是戏弄钻石操作员的不同部分。我在Compress::Zlib documentation中找到了一些似乎与我想要做的很接近的帮助,除了它试图解压缩所有内容(最后是未压缩文件的垃圾输出):

use strict ;
use warnings ;
use Compress::Zlib ;

# use stdin if no files supplied
@ARGV = '-' unless @ARGV ;

foreach my $file (@ARGV) {
    my $buffer ;

    my $gz = gzopen($file, "rb") 
         or die "Cannot open $file: $gzerrno\n" ;

    print $buffer while $gz->gzread($buffer) > 0 ;

    die "Error reading from $file: $gzerrno" . ($gzerrno+0) . "\n" 
        if $gzerrno != Z_STREAM_END ;

    $gz->gzclose() ;
}

我的修改是改为IO::Uncompress::Gunzip并让透明的解压缩工作:

#!/usr/bin/perl
use strict;
use warnings;

use IO::Uncompress::Gunzip qw(gunzip $GunzipError);

# use stdin if no files supplied
@ARGV = '-' unless @ARGV

foreach my $file (@ARGV) {
    my $z = new IO::Uncompress::Gunzip($file, "transparent", 1)
        or die "gunzip failed: $GunzipError\n";
    while(<$z>){
        print;
    }
    close($z);
}

这似乎只适用于读取和写入文件(例如zcat),但我还没有在我的脚本中测试它。