我编写了一些用于处理 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
支持透明解压缩:
如果设置了此选项并且输入文件/缓冲区不是压缩数据,模块将允许读取它。
此外,如果输入文件/缓冲区确实包含压缩数据,并且紧随其后有非压缩数据,设置此选项将使该模块将整个文件/缓冲区视为单个数据流。
我想基本上将透明解压缩插入到钻石运算符中,在加载每个文件和从文件输入读取一行之间。有谁知道我该怎么做?
我经常使用:
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
,请看这里的解释。
这也是我很久以来就想做的事情。直到最近我才学会如何稳健地做到这一点。
该方法不需要任何文件命名约定。相反,它检查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
打开的文件,然后根据结果选择一种方法。
我认为我最困难的是梳理钻石运算符的不同部分。我在
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),我现在已转而在大多数脚本中使用它。