逐行读取并逐行打印匹配项
Read line by line and print matches line by line
我是 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 '[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 .......
然后我也试了这个:
while read p; do
grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' | xargs > output.txt
done < input.txt
但没有帮助:(
也许还有另一种方式,我愿意接受 awk/sed/cut 或其他...:)
注意:可以有任意数量的错误代码(即XX:XXXX,一行中感兴趣的模式)。
% awk 'BEGIN{RS=": "};NR>1{printf "%s%s", , ([=10=]~/\n/)?"\n":" "}' input.txt
EU-1C0A TM-0401
MG-7688 DN-0A00 DN-0A52 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688
详细说明:
awk '
BEGIN{ RS=": " } # Set the record separator to colon-space
NR>1 { # Ignore the first record
printf("%s%s", # Print two strings:
, # 1. first field of the record (``)
([=11=]~/\n/) ? "\n" : " ")
# Ternary expression, read as `if condition (thing
# between brackets), then thing after `?`, otherwise
# thing after `:`.
# So: If the record ([=11=]) matches (`~`) newline (`\n`),
# then put a newline. Otherwise, put a space.
}
' input.txt
未修改问题的上一个答案:
% awk 'BEGIN{RS=": "};NR>1{printf "%s%s", , (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 && ~ /^[A-Z]{2}-[A-Z0-9]{4}$/ {printf "%s%s", , ([=13=]~/\n/)?"\n":" "}' input.txt
要保持您的 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
(读入的行)作为 stdin
到 grep
。这意味着 grep
将搜索 $p
的内容并将每个匹配项打印在自己的行上。
echo $(grep ...)
将 grep
输出中的换行符转换为空格,并在末尾添加一个换行符。由于每一行都会发生此循环,因此结果是在输出的单行上打印每个输入行的匹配项。
done < input.txt > output.txt
是正确的:您正在为整个循环提供输入并从中获取输出。您不需要在循环内重定向。
如果您知道每一行都包含正好 两个您要匹配的字符串实例,则另一种解决方案有效:
cat input.txt | grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' | xargs -L2 > output.txt
总是有 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
过于冗长。
如果没有匹配项,则不会打印任何内容。
在 Gnu awk 中。每条记录支持多个匹配项:
$ awk '
{
while(match([=10=], /[A-Z]{2}-[A-Z0-9]{4}/)) { # find first match on record
b=b substr([=10=],RSTART,RLENGTH) OFS # buffer the match
[=10=]=substr([=10=],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 之外的其他 awk,请将正则表达式 match
替换为:
while(match([=11=], /[A-Z][A-Z]-[A-Z0-9][A-Z0-9][A-Z0-9]/))
这是一个相当简单的 awk 解决方案,但它不是一个优雅的单行代码(许多 awk 解决方案往往如此)。它应该适用于每行任意数量的错误代码,并且错误代码定义为与给定正则表达式匹配的字段(白色 space 分隔的单词)。由于它不是时髦的单行代码,我将程序存储在一个文件中:
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
(默认为 space)only as needed 并使用输出记录分隔符ORS
(默认为换行符)仅当至少找到一个错误代码时。这样可以防止不必要的白色space.
请注意,我已将您的正则表达式从 [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
。
使用 AWK
解析 grep -n
grep -n -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' file | awk -F: -vi=0 '{
printf("%s%s", i ? (i == ? " " : "\n") : "", )
i =
}'
想法是加入 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 a character depending on conditional expression 测试第一个字段 </code> 的值。如果 <code>i
为零(第一个 迭代 ),它仅打印第二个字段 </code>。否则,如果第一个字段等于 <code>i
,它打印 space,否则打印换行符 ("\n"
)。在 space/newline 之后打印第二个字段。
打印下一个块后,第一个字段的值存储到 i
中以用于下一次迭代(行):i =
.
Perl
在 Perl 中解析 grep -n
use strict;
use warnings;
my $p = 0;
while (<>) {
/^(\d+):(.*)$/;
print $p == ? " " : "\n" if $p;
print ;
$p = ;
}
用法: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
我在此处的一个答案中看到了类似的解决方案。我还是决定 post 它更紧凑。
其中一个关键想法是使用 -l
开关
输出记录分隔符的值(如果已定义)在传递给 print
的最后一个参数之后打印。因此,脚本会打印所有匹配项(@_
、,特别是),然后是换行符。
@_
变量通常用作子程序参数数组。为了简洁起见,我在脚本中使用了它。
你总是可以保持非常简单:
$ 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
上面的代码会在每行的末尾添加一个空白字符,如果你关心的话可以避免...
我是 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 '[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 .......
然后我也试了这个:
while read p; do
grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' | xargs > output.txt
done < input.txt
但没有帮助:(
也许还有另一种方式,我愿意接受 awk/sed/cut 或其他...:)
注意:可以有任意数量的错误代码(即XX:XXXX,一行中感兴趣的模式)。
% awk 'BEGIN{RS=": "};NR>1{printf "%s%s", , ([=10=]~/\n/)?"\n":" "}' input.txt
EU-1C0A TM-0401
MG-7688 DN-0A00 DN-0A52 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688
详细说明:
awk '
BEGIN{ RS=": " } # Set the record separator to colon-space
NR>1 { # Ignore the first record
printf("%s%s", # Print two strings:
, # 1. first field of the record (``)
([=11=]~/\n/) ? "\n" : " ")
# Ternary expression, read as `if condition (thing
# between brackets), then thing after `?`, otherwise
# thing after `:`.
# So: If the record ([=11=]) matches (`~`) newline (`\n`),
# then put a newline. Otherwise, put a space.
}
' input.txt
未修改问题的上一个答案:
% awk 'BEGIN{RS=": "};NR>1{printf "%s%s", , (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 && ~ /^[A-Z]{2}-[A-Z0-9]{4}$/ {printf "%s%s", , ([=13=]~/\n/)?"\n":" "}' input.txt
要保持您的 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
(读入的行)作为stdin
到grep
。这意味着grep
将搜索$p
的内容并将每个匹配项打印在自己的行上。echo $(grep ...)
将grep
输出中的换行符转换为空格,并在末尾添加一个换行符。由于每一行都会发生此循环,因此结果是在输出的单行上打印每个输入行的匹配项。done < input.txt > output.txt
是正确的:您正在为整个循环提供输入并从中获取输出。您不需要在循环内重定向。
如果您知道每一行都包含正好 两个您要匹配的字符串实例,则另一种解决方案有效:
cat input.txt | grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' | xargs -L2 > output.txt
总是有 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
过于冗长。
如果没有匹配项,则不会打印任何内容。
在 Gnu awk 中。每条记录支持多个匹配项:
$ awk '
{
while(match([=10=], /[A-Z]{2}-[A-Z0-9]{4}/)) { # find first match on record
b=b substr([=10=],RSTART,RLENGTH) OFS # buffer the match
[=10=]=substr([=10=],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 之外的其他 awk,请将正则表达式 match
替换为:
while(match([=11=], /[A-Z][A-Z]-[A-Z0-9][A-Z0-9][A-Z0-9]/))
这是一个相当简单的 awk 解决方案,但它不是一个优雅的单行代码(许多 awk 解决方案往往如此)。它应该适用于每行任意数量的错误代码,并且错误代码定义为与给定正则表达式匹配的字段(白色 space 分隔的单词)。由于它不是时髦的单行代码,我将程序存储在一个文件中:
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
(默认为 space)only as needed 并使用输出记录分隔符ORS
(默认为换行符)仅当至少找到一个错误代码时。这样可以防止不必要的白色space.
请注意,我已将您的正则表达式从 [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
。
使用 AWK
解析grep -n
grep -n -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' file | awk -F: -vi=0 '{
printf("%s%s", i ? (i == ? " " : "\n") : "", )
i =
}'
想法是加入 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 a character depending on conditional expression 测试第一个字段 </code> 的值。如果 <code>i
为零(第一个 迭代 ),它仅打印第二个字段 </code>。否则,如果第一个字段等于 <code>i
,它打印 space,否则打印换行符 ("\n"
)。在 space/newline 之后打印第二个字段。
打印下一个块后,第一个字段的值存储到 i
中以用于下一次迭代(行):i =
.
Perl
在 Perl 中解析 grep -n
use strict;
use warnings;
my $p = 0;
while (<>) {
/^(\d+):(.*)$/;
print $p == ? " " : "\n" if $p;
print ;
$p = ;
}
用法: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
我在此处的一个答案中看到了类似的解决方案。我还是决定 post 它更紧凑。
其中一个关键想法是使用 -l
开关
输出记录分隔符的值(如果已定义)在传递给 print
的最后一个参数之后打印。因此,脚本会打印所有匹配项(@_
、,特别是),然后是换行符。
@_
变量通常用作子程序参数数组。为了简洁起见,我在脚本中使用了它。
你总是可以保持非常简单:
$ 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
上面的代码会在每行的末尾添加一个空白字符,如果你关心的话可以避免...