关于perl:仅打印行中的第一个单词

 2021-04-27 

Print only the first word in line

我需要以下perl代码的帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!perl -w
use strict;
use warnings;

open my $file, '<', 'ubb' or die $1;

my $spool = 0;
my @matchingLines;

while (<$file>) {
    if (/GROUPS/i) {
        $spool = 1;
        next;
    }
    elsif (/SERVERS/i) {
        $spool = 0;
        print map {"$_" } @matchingLines;
        @matchingLines = ();
    }
    if ($spool) {
        push (@matchingLines, $_);
    }
}
close ($file);

输出如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ADM                     LMID=GW_S4_1_PM,GW_S4_2_BM
                        GRPNO=1

ADM_TMS                 LMID=GW_S4_1_PM,GW_S4_2_BM
                        GRPNO=2
                        TMSNAME=TMS

ADM_1                   LMID=GW_S4_1_PM
                        GRPNO=11

ADM_2                   LMID=GW_S4_2_BM
                        GRPNO=12

DMWSG_Gateway_1         LMID=GW_S4_1_PM
                        GRPNO=101
                        ENVFILE="../GW_S4.Gateway.envfile"

DMWSG_Gateway_2         LMID=GW_S4_2_BM
                        GRPNO=201
                        ENVFILE="../GW_S4.Gateway.envfile"

DMWSG_1                 LMID=GW_S4_1_PM
                        GRPNO=106

DMWSG_2                 LMID=GW_S4_2_BM
                        GRPNO=206

但是我只想获取每行的第一个单词(例如ADMADM_TMSADM_1)。

请注意,该文件在此处打印的内容的上方和下方都有很多其他行。我只想对GROUPSSERVERS之间的行执行此操作。


我建议您对代码进行2次更改

注意:已使用问题中的样本数据(以及其他内容)对它们进行了测试。

I:提取push之前的第一个单词

更改此

1
push (@matchingLines, $_);

1
push (@matchingLines, /^(\\S+)/);

这会将每行的第一个单词而不是整行推入数组。

请注意,/^(\\S+)/$_ =~ /^(\\S+)/的简写。如果像7stud的答案中那样使用显式循环变量,则不能使用此简写形式,而应使用显式语法,例如$line =~ /^(\\S+)/或任何循环变量。

当然,您也可以按照7stud的答案中的建议使用split函数。

II:更改print的方式

更改此

1
print map {"$_" } @matchingLines;

变成

1
2
3
4
local $" ="\
"
;
print"@matchingLines \
"
;

$"指定使用双引号内的printsay打印数组时用于列表元素的定界符。

或者,根据TLP的建议,

1
2
$\\ = $/;
print for @lines;

1
2
3
print join("\
"
, @lines),"\
"

请注意,$/是输入记录分隔符(默认为换行符),$\\是输出记录分隔符(默认为未定义)。在每个print命令之后附加$\\

有关$/$\\$"的更多信息:

  • 请参见perldoc perlvar(只需使用CTRL F在该页面中找到它们)
  • 或者,您可以简单地在控制台上使用perldoc -v '$/'等获取这些信息。

关于可读性的注意事项

我不认为隐式正则表达式匹配,即/pattern/本身是不好的。

但是与变量匹配,即$variable =~ /pattern/更具可读性(因为您可以立即看到正在进行正则表达式匹配),并且对新手更友好,这是以简洁为代价的。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use 5.014;    #say()

my $fname = 'data.txt';
open my $INFILE, '<', $fname
    or die"Couldn't open $fname: $!";  #-->Not $1"

my $recording_on = 0;
my @matching_lines;

for my $line (<$INFILE>) {

    if ($line =~ /groups/i) {
        $recording_on = 1;
        next;
    }
    elsif ($line =~ /servers/i) {
        say for @matching_lines;  #say() is the same as print(), but it adds a newline at the end
        @matching_lines = ();
        $recording_on = 0;
    }

    if ($recording_on) {
        my ($first_word, $trash)  = split"", $line, 2;
        push @matching_lines, $first_word;
    }
}

close $INFILE;


您可以使用触发器运算符(范围)选择输入的一部分。此运算符的想法是,在LHS(左侧)返回true之前,它返回false,然后在RHS返回false之前,它返回true,然后将其重置。这有点像保存状态。

请注意,边缘线也包含在比赛中,因此我们需要将其删除。之后,使用doubleDown \\的想法并将/^(\\S+)/推入数组。将其与push一起使用的好处是,捕获正则表达式如果失败则返回一个空列表,当正则表达式不匹配时,这将为我们提供无警告的失败。

1
2
3
4
5
6
7
8
9
10
11
12
use strict;
use warnings;

my @matches;
while (<>) {
    if (/GROUPS/i .. /SERVERS/i) {    # flip-flop remembers the matches
        next if (/GROUPS/i or /SERVERS/i);
        push @matches, /^(\\S+)/;
    }
}

# @matches should now contain the first words of those lines