【问题标题】:Perl - Problem with splitting columns in tab delimited text file and replacing columns with new valuesPerl - 在制表符分隔的文本文件中拆分列并用新值替换列的问题
【发布时间】:2011-10-02 02:29:59
【问题描述】:

我有一个制表符分隔符。文本文件由许多行和列组成。我想更改前两列的内容,然后将修改后的文件写入新文件。
在更改之前,每行的前两列如下所示:

COLUMN1:                                              
dip:DIP-41935N|refseq:NP_056092|uniprotkb:Q96PU5    

COLUMN2:    dip:DIP-48957N|uniprotkb:P49281

我希望它们只包含每列末尾的 id 号,所以我希望它们如下所示:

COLUMN1:        Q96PU5          

COLUMN 2:       P49281

我已在选项卡上拆分行以获取各个列。然后拆分前 2 列以获得所需的 ID 号 ($prot_id)。然后我尝试用 ID 替换第 1 列和第 2 列的内容。但是,更改后的文件中的输出与我预期的不同。相反,它看起来像这样:

  COLUMN1:                                           
Q96PU5|refseq:NP_056092|uniprotkb:Q96PU5    

COLUMN 2:
P49281|uniprotkb:P49281

仅列的第一部分已被替换。我一直在玩这个几个小时,无法弄清楚我做错了什么。非常感谢任何帮助。 我的代码如下:

#!/usr/bin/perl  

use warnings;
use strict;


my $file = 'DIP.txt';

open(INFILE, $file) or die "Can't open file: $!\n";
open(my $outfile, '>', 'DIP_changed.txt'); 
my @lines = <INFILE>;


foreach $_ (@lines) {
    my @columns = split('\t', $_);

            my $col1 = $columns[0];
            my $col2 = $columns[1];


            my @split_col1 = split ('uniprotkb:', $col1);
            my @split_col2 = split ('uniprotkb:', $col2);

            my $prot_id1 = $split_col1[length(@split_col1)];
            my $prot_id2 = $split_col2[length(@split_col2)];

            print $prot_id1, "\n";

             s/$col1/$prot_id1/;
             s/$col2/$prot_id2/;

            print {$outfile} $_; 
}



exit;

【问题讨论】:

  • my $prot_id1 = $split_col1[length(@split_col1)]; 没有做您最可能想要的事情 - 可能是数组的最后一个索引。 length 返回字符串中的字符数。要索引数组中的最后一个元素,只需使用 'my $prot_id1 = $split_col1[ -1 ];`

标签: file perl split


【解决方案1】:

已经有一些不错的答案,但我想向您展示一个更简单的解决方案。这个脚本,你可以这样使用:

$ script.pl DIP.txt > DIP_changed.txt

而脚本本身真的很简单:

while (<>) {
    s/\S+uniprotkb:(\S+)/$1/;
    s/\S+uniprotkb:(\S+)/$1/;
    print;
}

不需要比这更复杂。

【讨论】:

  • 看起来很简单!这不会改变文件中包含“uniprot:”的每一列,而不是前两列吗?
  • @James 不,正则表达式匹配只执行一次,除非您添加 /g(全局)选项。所以这两个将改变前两个匹配项,仅此而已。
  • @TLP 但是如果前两个匹配项可能不在前两列中,在这种情况下,您将不会得到问题中指定的行为。
【解决方案2】:

试试这样的:

这是一个简洁的 Perl 习惯用法 - 像这样在正则表达式上匹配字符串

$columns[0]=~/:((\w|\d)*)$/;

(注意这里用括号定义了两个原子)并将匹配的结果(无论是第一个、第二个等原子中的什么)分配给一个数组 - 或者分配给一个数组中的一组标量变量数组列表,像这样:

($columns[0]) = $columns[0]=~/:((\w|\d)*)$/;

你看,你在正确的轨道上,但你让它变得比需要的更难:)

#!/usr/bin/perl  

use warnings;
use strict;

my $file = 'DIP.txt';

open(INFILE, $file) or die "Can't open file: $!\n";
open(my $outfile, '>', 'DIP_changed.txt');


foreach my $line (<INFILE>) {
    print "The input line is $line\n";
    my @columns = split('\t', $line);

    ($columns[0]) = $columns[0]=~/:((\w|\d)*)$/;
    ($columns[1]) = $columns[1]=~/:((\w|\d)*)$/;

    printf  "The output line is  %s\n", join ',', @columns;
    printf  $outfile join ',', @columns;

    }

【讨论】:

  • 我应该添加 - 如果您在每列中查找的部分(例如 Q96PU5)和选项卡之间有任何空格,则此正则表达式将不匹配。当我将您的示例数据复制并粘贴到文件中时,我不小心在其中放了一个空格,但找不到匹配项。我认为将正则表达式更改为: /:((\w|\d)*)\s*$/ 可以解决这个问题。 (\s* 匹配零个或多个空格字符,但由于它位于原子之后和字符串结尾“$”标记之前,因此空格不会包含在匹配中。)
  • 感谢您,它似乎确实有效。我不太明白这部分在做什么:
  • 我们试图匹配的字符串包含混合的数字和字母(例如 Q96PU5) \w 表示“任何单词字符”并且将匹配字母。 \d 表示任何数字和 |表示左右 (\w|\d) 将匹配任何一个字母或数字字符。 * 量词表示“匹配前面的事物零次或多次”,因此 (\w|\d)* 将匹配任何字母和数字序列,如 Q96PU5。使用 + 量词而不是 * 可能会更好,因为 + 将匹配 一个 或更多。
  • 然后把冒号 : 放在前面,后面加上字符串结束标记,你会得到这个 :((\w|\d)*)$ ,它将匹配任何看起来像:Q96PU5 在字符串的末尾。将其包裹在括号中意味着该实际匹配的内容,即匹配 (\w|\d)* 的内容将从匹配中返回。
  • DavidO 下面的回答类似。他使用了正则表达式 ([^:]+)$,这意味着匹配任何不是冒号 [^:] 重复一次或多次 + 后跟字符串结尾的字符。我几乎比我写的正则表达式更喜欢这个;它更像是说你的意思。我看到的唯一问题是它将匹配并在结果中包含任何可能存在于字符串结尾之前的空格。我发布的第一个正则表达式只会在出现这样的空白时中断。这就是为什么我添加了建议 /:((\w|\d)*)\s*$/ 的评论。
【解决方案3】:

ratsbane 的回答非常好,但您可能想知道在工作数小时后为什么您得到了您所做的答案。原因是 $col1 里面有一个管道。那是正则表达式中的“或”。因此,当您尝试替换正则表达式 $col1 时,您是在进行查找和替换

dip:DIP-41935N|refseq:NP_056092|uniprotkb:Q96PU5

现在作为一个正则表达式,它匹配什么?它只匹配

dip:DIP-41935N

所以那个是被替换的!

希望有帮助!

【讨论】:

  • 啊,酷。我从来没有停下来看为什么他的代码不工作。很容易忘记转义模式。
  • 很高兴知道,我就是想不通!所以没有办法使用基于我已经拥有的代码来解决列中的“管道”问题?
  • 是的,您可以通过以下方式解决此问题:s/\Q$col1\E/$prot_id1/;(当然第 2 列也是如此)。但最好还是使用@TLP 的解决方案。
  • 好的,但是出于好奇,\Q 和 \E 是什么意思?
  • 在\Q 和\E 中,元字符如|和 * 和 + 和 ?和朋友失去了特殊意义,被视为普通字符。所以当 /a|b/ 匹配 a 或 b 时,/\Qa|b\E/ 匹配三个字符的字符串“a|b”。
【解决方案4】:

可能没有很好的理由在一开始就吞下文件,而不是逐行处理它。逐行处理将更好地扩展。考虑到这一点,我会这样做:

use warnings;
use strict;


my $file = 'DIP.txt';

open my $in_fh, '<', $file or die $!;
open my $out_fh, '>', 'new' . $file or die $!;

while ( <$in_fh> ) {
    chomp;
    next unless length $_; # Skip blank lines.
    my ( @columns ) = split /\s+/, $_; # Split on whitespace (you may prefer \t).
    foreach my $column ( @columns ) {
        ( $column ) = $column =~ m{([^:]+)$};
    }
    local $" = "\t";
    print $out_fh "@columns\n";
}

首先,这在输入文件和输出文件上都使用了三个 arg 版本的 open。这是一个养成的好习惯。接下来,它使用词法文件句柄而不是旧的 fileglob 文件句柄。词法超出范围时会自动关闭,并且不会成为全局符号表的一部分。

接下来,脚本会读取文件并逐行处理,以避免乱码。如果文件可能会变大,或者您处于内存使用量非常高的环境中,这可能是有利的。除非你有充分的理由啜饮,否则不妨养成不啜食的习惯。

然后我在空白处拆分。您可以在标签上拆分。除非列中有嵌入的空格,否则任何一种方式都有效。然后我遍历这两列,匹配并捕获列末尾不是冒号的所有内容。或者换一种说法,最后一个冒号之后的所有内容。我将结果直接捕获到 $column 变量中,该变量为 @columns 中的相应元素起别名。这样,当我完成时,@columns 只会保存我的捕获。

最后,在处理完这两列之后,我们将 $" 本地化,为其分配一个制表符。这样,当我们通过将@columns 括在引号中来打印两列时,插值会自动再次在列之间粘贴一个制表符。如果您更喜欢不同的角色,您现在知道在哪里进行更改。

然后 while 循环移动到下一行。任何空行都将被跳过。

请参阅 perldoc open、perlretut、perlvar 和 perlop,了解三参数 open 和词法文件句柄的解释、正则表达式的解释、Perl 的特殊变量(如 $")以及引号内插的工作原理。

好问题!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-01
    • 2020-05-26
    • 1970-01-01
    • 2021-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多