如何删除特定列中与另一个文件匹配元素的行

时间:2018-01-11 07:06:39

标签: perl

我有一个包含许多文本文件的文件夹,如下所示:

ATOM   5132 HG22   ILE   B 162     -10.906  60.208   9.028  1.00  0.00           H  
ATOM   5133 HG23   ILE   B 162     -11.193  58.585   9.650  1.00  0.00           H  
ATOM   5134 HD11   ILE   B 162      -9.888  57.413   9.161  1.00  0.00           H  
ATOM   5135 HD12   ILE   B 162      -8.448  57.195   8.181  1.00  0.00           H  
ATOM   5136 HD13   ILE   B 162      -9.913  56.300   7.799  1.00  0.00           H  
HETATM 5138 ZN    ZN A 190      30.757  32.494  -1.721  1.00  0.00          ZN  
HETATM 5139  C1  UQ1 B 501       2.889  33.364  18.810  1.00  0.00           C  
HETATM 5140  O1  UQ1 B 501       2.849  32.140  19.037  1.00  0.00           O  
HETATM 5141  C2  UQ1 B 501       4.162  33.930  18.303  1.00  0.00           C  
HETATM 5142  O2  UQ1 B 501       5.209  33.069  18.099  1.00  0.00           O  
HETATM 5143  CM2 UQ1 B 501       5.802  32.349  19.180  1.00  0.00           C  
HETATM 5144  C3  UQ1 B 501       4.270  35.396  18.017  1.00  0.00           C  

我有一个包含不同数量符号的文件ions_solvents_cofactors,如下所示:

ZN
008
03S
06C
0KA
0NG
0NM
0QE
144
1CL
1SA
1TP
202
21H
2A6
2BM
2F2
2HE
2HP
2MO
2NO
2PA
2PN
2PO
2T8

我写了一个程序 应该打开并阅读当前文件夹中的每个.txt文件,并在第1列为ions_solevnts_cofactors时删除第4列与文件HETATM中的任何值匹配的行。

它给了我这个错误

rm: cannot remove `ATOM': No such file or directory
rm: cannot remove `1459': No such file or directory
rm: cannot remove `HB': No such file or directory
rm: cannot remove `ILE': No such file or directory

这是脚本

#!/usr/local/bin/perl

use strict;
use warnings;

$dirname = '.';
opendir( DIR, $dirname ) or die "cannot open directory";
@files = grep( /\.txt$/, readdir( DIR ) );

foreach $files ( @files ) {

    open( FH, $files ) or die "could not open $files\n";
    @file_each = <FH>;
    close FH;

    close DIR;

    my @ion = ();

    my $ionfile = 'ions_solvents_cofactors';

    open( ION, $ionfile ) or die "Could not open $ionfile, $!";
    my @ion = <ION>;
    close ION;

    for ( my $line = 0; $line <= $#file_each; $line++ ) {

        chomp( $file_each[$line] );

        if ( $file_each[$line] =~ /^HETATM/ ) {
            @is = split '\s+', $file_each[$line];
            chomp $is[3];
        }

        foreach ( $file_each[$line] ) {    # line 39

            if ( "@ion" =~ $is[3] ) {
                system( "rm $file_each[$line]" );
            }
        }
    }
}

我希望脚本覆盖每个文本文件,并只读取以HETATM开头的第四列。如果它与文件ions_solvents_cofactors中的任何元素匹配,则应删除此行。

所以,例如

HETATM 5138 ZN    ZN A 190      30.757  32.494  -1.721  1.00  0.00          ZN

此行应从文件中删除,因为ZN匹配。

2 个答案:

答案 0 :(得分:1)

有许多必要的改进,以及一些直接错误。

首先是一个简单的工作代码,从问题

中得出一些假设
use warnings;
use strict;
use feature 'say';

#use File::Glob ':bsd_glob';   # using \Q..\E in glob, no need for this
use File::Copy qw(move);
use List::MoreUtils qw(any);

my $dirname = shift @ARGV || '.';

my $ionfile = 'ions_solvents_cofactors';
open my $fh, '<', $ionfile or die "Can't open $ionfile: $!";
my @ion_terms = <$fh>;
chomp @ion_terms;

my @files = glob "\Q$dirname\E/*.txt";

foreach my $file (@files) {
    open my $fh, '<', $file or do {
       warn "Can't open $file: $!";
       next;
    };
    my $outfile = $file . '_new';
    open my $fh_out, '>', $outfile or die "Can't open $outfile: $!";

    while (<$fh>) {
        next if not /^HETATM/;
        my @fields = split;
        next if any { $fields[3] =~ /$_/ } @ion_terms;
        print $fh_out $_; 
    }   

    # Uncomment to overwrite, when thoroughly tested
    #move $outfile, $file or warn "Can't move $outfile to $file: $!"
}

评论

  • 参考文件只需打开一次;把它弄出循环

  • 无法将数组“初始化”为emtpy,例如my @ion = ()。当你用my @ion声明它时,你会得到它。 (如果需要清除数组,则@ary = ();有意义)

  • 使用词法文件句柄open my $fh, ...,而不是typeglobs FH。使用词法文件句柄。使用词法文件句柄。请参阅Typeglobs and Filehandles的结尾并阅读open

  • 几乎不需要C风格的foreach循环。如果你需要迭代索引,for my $i (0..$#ary)很棒。但大多数时候你需要元素,比如这里

  • 您应使用\s+代替split中使用的' '模式,这也是split的默认设置。这就是上面的代码不需要它的原因,因为split;split ' ', $_;相同

  • @file_each不是文件中行的好名称

  • 直接错误:您正在尝试rm文件中的!更好的命名将有助于

  • 您对opendirreaddir的使用很好(除了DIR而不是词法文件句柄!!),但glob在这里更好。 修改:我在glob中使用\Q..\E,以防止可能的注入错误,即异常目录名称触发意外处理。由于这些也是逃避空间,因此不再需要File::Glob及其bsd_glob()

  • 我使用List::MoreUtils::any来查找@ion_terms中的任何元素是否满足块中的条件,以匹配$fields[3]。这也可以使用grep完成。此外,如果您的术语列表与显示的一样短,则可以使用它组装正则表达式模式

    my $re = join '|', { quotemeta } @ion_terms;  # before the loop
    next if $fields[3] =~ /$re/;
    
  • 上面的一些代码可以更简洁,更简单地编写

答案 1 :(得分:0)

如果我不清楚我对你的建议,我很抱歉 上一个问题 How to delete lines that match elements from another file。我建议你应该发布另一个问题,因为你提出了新的问题,但我打算你应该从我们所处的位置开始工作,而你似乎已经放弃了所有这些并重新开始使用原始代码,包括炮轰rm错误地认为它会从文件中删除一行

现在您已经显示了ions_solvents_cofactors的完整版本,我可以看到我的假设是正确的,您提出的唯一其他问题是,只应从PDB中删除以HETATM开头的行文件,你在你的问题中没有说出来

这与我之前的解决方案非常相似,但我添加了对HETATM数据的检查。我还改进了日志输出,以便说明来自ions_solvents_cofactors的哪个值匹配导致删除

请尝试使用此新代码,并在发现任何问题时进行报告

use strict;
use warnings 'all';

use File::Glob ':bsd_glob';
use Tie::File;

my %matches = do {
    open my $fh, '<', 'ions_solvents_cofactors';
    local $/;
    map { $_ => 1 } split ' ', <$fh>;
};

for my $pdb ( glob '*.txt' ) {

    tie my @file, 'Tie::File', $pdb or die $!;

    for ( my $i = 0; $i < @file; ) {

        my ($id, undef, undef, $col4) = split ' ', $file[$i];

        if ( $id eq 'HETATM' and $col4 and $matches{$col4} ) {

            printf qq{Removing line %d from "%s" (matches %s)\n},
                    $i+1, $pdb, $col4;

            splice @file, $i, 1;
        }
        else {
            ++$i;
        }
    } 
}

输出

Removing line 6 from "test.txt" (matches ZN)