Linux:逐行读取并逐行打印

我是shell脚本的新手,如果能在下面的问题上得到一些帮助,那将是非常棒的。

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

例如:

$ 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 '[AZ]\{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 ....... 

然后我也试过这个:

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

但没有帮助:(

也许还有另一种方式,我打开awk / sed / cut或其他… 🙂

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

总有perl! 这将抓住每行的任何数量的匹配。

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

-e perl代码由编译器运行, -n一次运行一行, -l自动chomps该行并添加一个换行符以打印。

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

如果没有匹配,这将不会打印任何东西。

 % 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 

编辑:与保障: -injection(thx @ e0k)。 测试记录分隔符后面的第一个字段看起来像我们预期的那样。

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

你可以永远保持它非常简单:

 $ 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 

以上将在每行的末尾添加一个空白的char,如果你在意的话,可以避免使用…

为了保持你的grep模式,这是一个方法:

 while IFS='' read -rp; do echo $(grep -o '[AZ]\{2\}-[A-Z0-9]\{4\}' <<<"$p") done < input.txt > output.txt 
  • while IFS='' read -rp; do while IFS='' read -rp; do是逐行读入变量的标准方式。 看,例如, 这个答案 。
  • grep -o '[AZ]\{2\}-[A-Z0-9]\{4\}' <<<"$p"运行你的grep并打印匹配。 <<<"$p"是一个“这里的字符串” ,它提供字符串$p (读入的行)作为stdingrep 。 这意味着grep将搜索$p的内容,并打印每个匹配在自己的行。
  • echo $(grep ...)grep输出中的换行符转换为空格,并在结尾处添加换行符。 由于这个循环发生在每一行,所以结果是在输出的一行上打印每个输入行的匹配。
  • done < input.txt > output.txt是正确的:您提供输入,并从整个循环中获取输出。 循环内不需要重定向。

另一种解决方案,如果你知道每行都会包含你想匹配的字符串的两个实例:

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

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

codes.awk

 #!/usr/bin/awk -f { m=0; for (i=1; i<=NF; ++i) { if ( $i ~ /^[AZ][AZ]-[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 (默认为换行符)。 这可以防止不必要的空白。

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

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

用AWK解析grep -n

 grep -n -o '[AZ]\{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初始化字段分隔符 ( -F: -vi=0i变量( -vi=0 ),然后逐行处理grep命令的输出。

它根据测试第一个字段$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 '[AZ]\{2\}-[A-Z0-9]\{4\}' file | perl script.pl grep -n -o '[AZ]\{2\}-[A-Z0-9]\{4\}' file | perl script.pl

单线

但是Perl实际上非常灵活和强大,你可以用一条线完全解决问题:

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

我在这里看到了类似的答案之一。 不过,我决定发布它,因为它更紧凑。

其中一个关键的想法是使用-l开关

  1. 自动chomps输入记录分隔符$/ ;
  2. 指定输出记录分隔符$\的值为$/ (默认为换行符)

输出记录分隔符的值(如果已定义)将在传递给print的最后一个参数之后print 。 因此,该脚本会打印所有匹配( 特别是 @_ ),然后是换行符。

@_变量通常用作子程序参数数组。 我只是为了简短而在剧本中使用过它。

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

 $ awk ' { while(match($0, /[AZ]{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。

如果你想使用其他的awk awk,把正则表达式替换成:

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