![如何按类别识别、降序排序并显示前 10 个文本块](https://linux22.com/image/96559/%E5%A6%82%E4%BD%95%E6%8C%89%E7%B1%BB%E5%88%AB%E8%AF%86%E5%88%AB%E3%80%81%E9%99%8D%E5%BA%8F%E6%8E%92%E5%BA%8F%E5%B9%B6%E6%98%BE%E7%A4%BA%E5%89%8D%2010%20%E4%B8%AA%E6%96%87%E6%9C%AC%E5%9D%97.png)
我们系统内实体上完成的交易历史如下所示:
1 BYM1 TSTAB 09NOV 0035 CAB
Sometext 01
2 BYM1 TSTAB 09NOV 0035 CAB
Can be done - question
3 BYM1 TSTAB 09NOV 0035 CAB
Sometext 02
Sometext 03
6 BYM3 TSTAA 09NOV 0400 CAA
Some 04 text 04
7 BYM3 TSTAA 10NOV 0455 CAC
Sometext 06
Sometext 06 line 2
8 BYM3 TSTAA 10NOV 0455 CAC
Sometext 07
9 BYM2 TSTAC 10NOV 0619 CAD
Some 08 text 0008 ABCD
Some 08 text 0008 BB00
Some 08 text 0008 CC00
Some 08 text 0008 DD00
Some 08 text 0008 EE00
10 BYM2 TSTAC 10NOV 0627 CAD
Something BBBBBSSDGFSDSF
11 BYM2 TSTAC 10NOV 0627 CAD
Something else
12 BYM2 TSTAC 10NOV 0627 CAD
What text here
13 BYM4 TSTAC 10NOV 0711 CAD
Tired figuring out
19 BYM3 TSTAA 11NOV 0438 CAE
Some 04 text 05 05 05
20 BYM3 TSTAA 11NOV 0441 CAF
Not so confidential now
21 BYM3 TSTAA 11NOV 0441 CAF
Some 00 text 0009 X1X2
43 BYM3 TSTAA 11NOV 0441 CAD
Some 0A text 0009 ABCD
44 BYM3 TSTAA 11NOV 0441 CAD
Some 1B text
45 BYM3 TSTAA 12NOV 1455 CAC
Something 0AADDBB
8782 BYM3 TSTAA 12NOV 1610 CAD
Something 0AADDBB
8830 BYM3 TSTAA 12NOV 1612 CAA
Something 0AADDBB
9999 BYM3 TSTAA 12NOV 1722 CAA
Something 0AADDBB
文本块以前 4 个字符包含数字的行开始。(该数字实际上是一个连续的序列号,每个交易都以此为索引)。块的(交易的)类别由包含数字的行中的最后三个字符定义。
我正在寻找 awk、sed (、vi、grep) 脚本来搜索属于“类别”的文本块,按索引(数字)的降序对结果块进行排序,显示我要求的块数为了。
例如,如果我想搜索“CAD”类别的 4 个块,我希望看到的输出是:
8782 BYM3 TSTAA 12NOV 1622 CAD
Something 0AADDBB
44 BYM3 TSTAA 11NOV 0441 CAD
Some 1B text
43 BYM3 TSTAA 11NOV 0441 CAD
Some 0A text 0009 ABCD
13 BYM4 TSTAC 10NOV 0711 CAD
Tired figuring out
我怎样才能达到这个目的。任何帮助将不胜感激 :-)
答案1
这是 gawk(GNU;即大多数“Linux”系统上的awk
版本)的解决方案。awk
假设$cat
设置为您要搜索的类别,并$num
设置为您要显示的记录数。
awk -vRS='\n[ 0-9][ 0-9][ 0-9][0-9] ' -vcat="$cat" -vnum="$num" \
' BEGIN { first=1; rec_ind=0}
{ if (first) {
rec = $0
first=0
} else {
rec = save_seq $0
}
findnl = index(rec, "\n")
if (findnl < 7) exit
thiscat = substr(rec, findnl-3, 3)
if (cat == thiscat) records[++rec_ind] = rec
if (length(RT) == 0) {
# print "This should be the last record."
save_seq = "Does not matter"
} else if (length(RT) == 6) {
save_seq = substr(RT, 2, 5)
} else {
print "Invalid RT: len =", length(RT)
exit
}
}
END { num_recs = asort(records, sorted_records, "@val_num_desc")
if (num < num_recs) num_recs = num
for (i=1; i<=num_recs; i++) {
print sorted_records[i]
}
}
'
笔记:
-vRS='\n[ 0-9][ 0-9][ 0-9][0-9] '
将 awk 的 RS(记录分隔符)变量设置为由换行符、后跟最多四位数字的整数序列号和空格组成的正则表达式。我包含换行符是因为您的数据在行内部有四位数字(后跟空格),它们不会被解释为记录分隔符。请注意,这个正则表达式有点草率,因为它会接受007
和12 4
。将其设置为 awk 的记录分隔符意味着您的每个“事务”都将被视为单个 awk 记录,即使它包含多行。有几个缺点:
- 由于 RS 模式在开头包含换行符,因此
1
数据开头的 不会被识别为记录分隔符。 - 既然这是记录分隔器模式,它不被视为记录的一部分,即使它包含重要信息。
我们将处理这些问题。
- 由于 RS 模式在开头包含换行符,因此
-vcat="$cat"
并-vnum="$num"
类似地设置 awk 变量cat
和num
到相应 shell 变量的值。BEGIN { first=1; rec_ind=0}
将标志初始化first
为 true (1),这样我们就可以识别第一条记录并对其进行特殊处理,并将记录索引 (rec_ind
) 初始化为 0,以累积与所需类别匹配的记录。if (first)
为 true(我们正在处理第一条记录),设置rec
等于 awk 记录,$0
。请记住,这包括直到(但不包括)以四位数开头的下一行的所有行。此外,它还包括第一行开头的四位数字。然后我们将first
标志设置为 false (0)。如果这不是第一条记录,则它缺少四位数字(因为这是记录分隔器
rec
),因此我们通过将保存的序列号 (save_seq
) 与连接起来来构造记录 ( )$0
。(我save_seq
稍后会讨论。)findnl = index(rec, "\n")
查找记录中的第一个换行符(请记住,记录包含多行)。如果从开头开始少于 7 个字符,则没有空间容纳序列号和类别(不重叠),更不用说其他字段了,因此这是一个错误。否则,thiscat
从第一个换行符之前的最后三个字符(即交易第一行的最后三个字符)中提取该记录的类别 ( )。然后,如果thiscat
与我们要查找的类别匹配,则将记录保存在records
数组中。RT
RS
是记录终止符 —与当前记录末尾的模式匹配的字符。不幸的是,当前记录的终结者实际上是下一个记录的开始。如果当前记录是最后一条记录,则将RT
为空字符串(长度为0);否则,它的长度应始终为 6 个字符(一个换行符、四个空格或数字字符以及一个空格)。提取最后五个字符(即丢弃换行符)并将其保存为save_seq
,因为它是下一个事务的序列号。- 当我们到达数据末尾时,对记录进行排序(对值进行排序,将它们视为数字,按降序排列)。然后打印最多
num
其中的一个。
答案2
遵循 Linux 原则“一项任务——一种工具”:
仅打印必要的块(如示例中所示
CAD
)sed '/^\s*[0-9].*CAD/!d;:a;N;/\n\s*[0-9]/! s/\n/\x0/;ta;P;D'
按相反顺序排序
sort -rn
只取第一个询问的块(如示例所示
4
)head -4
请注意,大多数 Linux 命令都是通过线(不是块\n
) 因此,通过将ew line 更改为 null-symbol( )将这些内容转换为行,\x0
然后通过t。
所以,所有行:
sed '/^\s*[0-9].*CAD/!d;:a;N;/\n\s*[0-9]/! s/\n/\x0/;ta;P;D' test.txt |
sort -rn |
head -4 |
tr '\0' '\n'
我喜欢G-Man 答案改变R
流动S
分离器的想法但这不太适合这种情况。用普通方法做更简单
awk '
/^[ 0-9]{4} /{ #for start block string
if($NF==cat){ #if it is a needed block
idx=$1
BLOCK[idx]=$0 #put line onto array with asigned index
}
else
idx=0 #otherways asign index to 0
next #end itteration, go to start with next line
}
idx{ #pass inappropriate blocks (with 0-index)
BLOCK[idx]=BLOCK[idx] "\n" $0 #add line to array element with index
}
END{ #when finish all lines
for(i=0;i<num;i++){ #do num times
max=0 #asing `max` variable to min value
for(idx in BLOCK){ #for each index in array
idx=idx+0 #convert string index into decimal
if(idx>max)
max=idx #find maximum index (field No.1 in block)
}
if(!max)
exit #exit script if array empty (no more blocks)
print BLOCK[max] #print block with maximum index
delete BLOCK[max] #remove array element for furure search
}
}' cat="CAD" num=4 test.txt
答案3
例如,假设您的块开始可以通过以数字开头的一行 6 个字段来检测,并且您的数据不包含字符代码\001
(control-a),则您可以将块的所有行连接到一行中,替换这个任意代码的换行符。然后对行进行排序,取出前 4 行并再次用换行符替换代码。
#!/bin/bash
num=${1?number} cat=${2?category}
awk -vcat="$cat" '
/^ *[0-9]+ / && NF==6 { ok = ($NF==cat)
if(ok && sep!="")sep = "\n"
}
ok { printf "%s%s",sep,$0; sep = "\001" }
END { if(sep!="")printf "\n" }' |
sort -nr -k1,1 | head -"$num" |
tr '\001' '\n'
$NF
如果字段(最后一个字段)是所需的类别,则 awk 将连接行。该sep
变量最初为空,""
然后变为\001
块内部,并且\n
当新块开始时。最后添加最后一个换行符,除非没有匹配项。
答案4
尝试更改 v= 和 num= 的值
$ awk '$NF==v{F=1;print;next}F&&NF!=6{print}F&&NF==6{F=0}' v="CAC" test.txt | awk '$NF~v{val=j++;F=1}F{Arr[val]=Arr[val]"\n"$0}END{n=asorti(Arr,S_Arr);for(i=n;i>=n-num;i--){print Arr[i]}}' v="CAC" num=4
45 BYM3 TSTAA 12NOV 1455 CAC
Something 0AADDBB
8 BYM3 TSTAA 10NOV 0455 CAC
Sometext 07
7 BYM3 TSTAA 10NOV 0455 CAC
Sometext 06
Sometext 06 line 2
$ awk '$NF==v{F=1;print;next}F&&NF!=6{print}F&&NF==6{F=0}' v="CAD" test.txt | awk '$NF~v{val=j++;F=1}F{Arr[val]=Arr[val]"\n"$0}END{n=asorti(Arr,S_Arr);for(i=n;i>=n-num;i--){print Arr[i]}}' v="CAD" num=4
8782 BYM3 TSTAA 12NOV 1610 CAD
Something 0AADDBB
44 BYM3 TSTAA 11NOV 0441 CAD
Some 1B text
43 BYM3 TSTAA 11NOV 0441 CAD
Some 0A text 0009 ABCD
13 BYM4 TSTAC 10NOV 0711 CAD
Tired figuring out