逐行阅读并逐行打印匹配

时间:2016-12-09 19:03:20

标签: linux bash shell grep text-processing

我是shell脚本的新手,如果我能从下面的问题中获得一些帮助,那就太棒了。

我想逐行读取文本文件,并将该行中所有匹配的模式打印到新文本文件中的一行。

例如:

public void aUserExists(String username) throws Throwable {
}

预期输出如下:

$ cat input.txt

SYSTEM ERROR: EU-1C0A  Report error -- SYSTEM ERROR: TM-0401 DEFAULT Test error
SYSTEM ERROR: MG-7688 DEFAULT error -- SYSTEM ERROR: DN-0A00 Error while getting object -- ERROR: DN-0A52 DEFAULT Error -- ERROR: MG-3218 error occured in HSSL
SYSTEM ERROR: DN-0A00 Error while getting object -- ERROR: DN-0A52 DEFAULT Error
SYSTEM ERROR: EU-1C0A  error Failed to fill in test report -- ERROR: MG-7688

我尝试了以下代码:

$ cat output.txt

EU-1C0A TM-0401
MG-7688 DN-0A00 DN-0A52 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688

产生了这个输出:

while read p; do
    grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' | xargs
done < input.txt > output.txt

然后我也尝试了这个:

EU-1C0A TM-0401 MG-7688 DN-0A00 DN-0A52 MG-3218 DN-0A00 DN-0A52 EU-1C0A MG-7688 .......

但没有帮助:(

也许有另一种方式,我愿意接受awk / sed / cut等等......:)

注意:可以有任意数量的错误代码(即XX:XXXX,单行感兴趣的模式)。

8 个答案:

答案 0 :(得分:5)

% awk 'BEGIN{RS=": "};NR>1{printf "%s%s", $1, ($0~/\n/)?"\n":" "}' input.txt 
EU-1C0A TM-0401
MG-7688 DN-0A00 DN-0A52 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688

longform中的说明:

awk '
    BEGIN{ RS=": " } # Set the record separator to colon-space
    NR>1 {           # Ignore the first record
        printf("%s%s", # Print two strings:
            $1,      # 1. first field of the record (`$1`)
            ($0~/\n/) ? "\n" : " ")
                     # Ternary expression, read as `if condition (thing
                     # between brackets), then thing after `?`, otherwise
                     # thing after `:`.
                     # So: If the record ($0) matches (`~`) newline (`\n`),
                     # then put a newline. Otherwise, put a space.
    }
' input.txt 

以前回答未经修改的问题:

% awk 'BEGIN{RS=": "};NR>1{printf "%s%s", $1, (NR%2==1)?"\n":" "}' input.txt 
EU-1C0A TM-0401
MG-7688 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688

编辑:防范: - 注入(thx @ e0k)。测试记录分隔符之后的第一个字段看起来像我们预期的那样。

awk 'BEGIN{RS=": "};NR>1 && $1 ~ /^[A-Z]{2}-[A-Z0-9]{4}$/ {printf "%s%s", $1, ($0~/\n/)?"\n":" "}' input.txt

答案 1 :(得分:4)

永远都是perl!这将每行抓取任意数量的匹配。

perl -nle '@matches = /[A-Z]{2}-[A-Z0-9]{4}/g; print(join(" ", @matches)) if (scalar @matches);' output.txt

-e perl代码由编译器运行 -n一次运行一行并且 -l会自动选择该行并为打印添加换行符。

正则表达式与$_隐式匹配。所以@matches = $_ =~ //g过于冗长。

如果没有匹配,则不会打印任何内容。

答案 2 :(得分:2)

你可以随时保持简单:

$ awk '{o=""; for (i=1;i<=NF;i++) if ($i=="ERROR:") o=o$(i+1)" "; print o}' input.txt
EU-1C0A TM-0401
MG-7688 DN-0A00 DN-0A52 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688

上面会在每行的末尾添加一个空白字符,如果你关心的话,可以避免使用...

答案 3 :(得分:1)

为了保持grep模式,这是一种方式:

while IFS='' read -r p; do
    echo $(grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' <<<"$p")
done < input.txt > output.txt
  • while IFS='' read -r p; do是逐行读入变量的标准方法。例如,参见this answer
  • grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' <<<"$p"运行你的grep并打印匹配。 <<<"$p""here string",它将$p(已读入的行)提供为stdingrep。这意味着grep将搜索$p的内容并在其自己的行上打印每个匹配。
  • echo $(grep ...)grep输出中的换行符转换为空格,并在末尾添加换行符。由于每个行都会发生这种循环,因此结果是在输出的一行上打印每个输入行的匹配。
  • done < input.txt > output.txt是正确的:您正在为整个循环提供输入和输出。您不需要在循环中重定向。

答案 4 :(得分:1)

如果你知道每一行都包含完全你要匹配的两个字符串实例,那么另一种解决方案是有效的:

cat input.txt | grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' | xargs -L2 > output.txt

答案 5 :(得分:1)

这是一个非常简单的awk解决方案,但它不是一个优雅的单行程序(因为许多awk解决方案往往是)。它应该与每行的任意数量的错误代码一起使用,并将错误代码定义为与给定正则表达式匹配的字段(空格分隔的单词)。由于它不是一个时髦的单行,我将程序存储在一个文件中:

<强> codes.awk

#!/usr/bin/awk -f
{
    m=0;
    for (i=1; i<=NF; ++i) {
        if ( $i ~ /^[A-Z][A-Z]-[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]$/ ) {
            if (m>0) printf OFS
            printf $i
            m++
        }
    }
    if (m>0) printf ORS
}

你会像

那样运行
$ awk -f codes.awk input.txt

我希望你觉得它很容易阅读。它为每行输入运行一次块。它迭代每个字段并检查它是否与正则表达式匹配,然后打印字段(如果匹配)。到目前为止,变量m会跟踪当前行上匹配字段的数量。这样做的目的是仅在需要时在匹配的字段之间打印输出字段分隔符OFS(默认为空格)并使用输出记录分隔符ORS(a仅当找到至少一个错误代码时才默认使用新行。这可以防止不必要的空白区域。

请注意,我已将正则表达式从[A-Z]{2}-[A-Z0-9]{4}更改为[A-Z][A-Z]-[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]。这是因为旧awk不会(或至少可能不)支持interval expressions{n}部分)。但是,您可以将[A-Z]{2}-[A-Z0-9]{4}gawk一起使用。您可以根据需要调整正则表达式。 (在awk和gawk中,正则表达式由/分隔。)

正则表达式/[A-Z]{2}-[A-Z0-9]{4}/将匹配包含您的XX-XXXX字母和数字模式的任何字段。您希望该字段与正则表达式完全匹配,而不仅仅是 include 匹配该模式的内容。为此,^$标记字符串的开头和结尾。例如,/^[A-Z]{2}-[A-Z0-9]{4}$/(使用gawk)将匹配US-BOTZ,但不匹配USA-ROBOTS。如果没有^$USA-ROBOTS 匹配,因为它包含与正则表达式匹配的子字符串SA-ROBO

答案 6 :(得分:1)

使用AWK

解析grep -n
grep -n -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' file | awk -F: -vi=0 '{
  printf("%s%s", i ? (i == $1 ? " " : "\n") : "", $2)
  i = $1
}'

我们的想法是加入grep -n

输出中的行
1:EU-1C0A
1:TM-0401
2:MG-7688
2:DN-0A00
2:DN-0A52
2:MG-3218
3:DN-0A00
3:DN-0A52
4:EU-1C0A
4:MG-7688

按行号。 AWK初始化field separator-F:)和i变量(-vi=0),然后逐行处理grep命令的输出。

prints一个字符取决于conditional expression,用于测试第一个字段$1的值。如果i为零(第一个迭代),则仅打印第二个字段$2。否则,如果第一个字段等于i,则会打印一个空格,否则为换行符("\n")。在空格/换行符之后,将打印第二个字段。

打印下一个块后,第一个字段的值将存储到i以进行下一次迭代(行):i = $1

的Perl

在Perl

中解析grep -n
use strict;
use warnings;

my $p = 0;

while (<>) {
  /^(\d+):(.*)$/;
  print $p == $1 ? " " : "\n" if $p;
  print $2;
  $p = $1;
}

用法:grep -n -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' file | perl script.pl

单行

但Perl实际上非常灵活和强大,您只需一行即可完全解决问题:

perl -lne 'print @_ if @_ = /([A-Z]{2}-[A-Z\d]{4})/g' < file

我在其中一个答案中看到了类似的解决方案。我仍然决定发布它,因为它更紧凑。

其中一个主要想法是使用-l开关

  1. 自动选择输入记录分隔符$/;
  2. 将输出记录分隔符$\指定为$/的值(默认为换行符)
  3. 输出记录分隔符的值(如果已定义)将在传递给print的最后一个参数后打印。因此,脚本会打印所有匹配项(@_,特别是),后跟换行符。

    @_变量通常用作子例程参数的数组。我只是为了简洁而在脚本中使用它。

答案 7 :(得分:0)

在Gnu awk。支持每条记录的多个匹配:

$ awk '
{
    while(match($0, /[A-Z]{2}-[A-Z0-9]{4}/)) {  # find first match on record
        b=b substr($0,RSTART,RLENGTH) OFS       # buffer the match
        $0=substr($0,RSTART+RLENGTH)            # truncate from start of record
    }
    if(b!="") print b                           # print buffer if not empty
    b=""                                        # empty buffer
}' file
EU-1C0A TM-0401 
MG-7688 DN-0A00 DN-0A52 MG-3218 
DN-0A00 DN-0A52 
EU-1C0A MG-7688 

下行:每张打印记录的末尾都会有额外的OFS。

如果您想使用除Gnu awk之外的其他awks,请将正则表达式match替换为:

while(match($0, /[A-Z][A-Z]-[A-Z0-9][A-Z0-9][A-Z0-9]/))