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

问题描述 投票:0回答:3

我编写了一些用于处理 FASTA/FASTQ 文件的脚本(例如 fastx-length.pl),但希望使它们更加通用,并接受压缩和未压缩文件作为命令行参数和标准输入(这样当您向脚本扔随机文件时脚本“就可以工作”)。对我来说,同时处理未压缩和压缩文件(例如压缩读取文件、未压缩组装基因组)是很常见的,并且像

<(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
支持透明解压缩:

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

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

我想基本上将透明解压缩插入到钻石运算符中,在加载每个文件和从文件输入读取一行之间。有谁知道我该怎么做?

fasta fastq compression perl
3个回答
6
投票

我经常使用:

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
,请看这里的解释


2
投票

这也是我很久以来就想做的事情。直到最近我才学会如何稳健地做到这一点。

该方法不需要任何文件命名约定。相反,它检查gzip幻数,即0x1f8b。它需要将每个文件的前两个字节作为二进制流读取(使用一个非常漂亮的函数,称为 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
打开的文件,然后根据结果选择一种方法。


0
投票

我认为我最困难的是梳理钻石运算符的不同部分。我在

Compress::Zlib
文档中找到了一些帮助,这似乎与我想做的很接近,除了它尝试解压缩所有内容(最终导致未压缩文件的垃圾输出):

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),我现在已转而在大多数脚本中使用它。

© www.soinside.com 2019 - 2024. All rights reserved.