我想过滤许多文本文件。每个文件都包含一个很长的数字列表。必须根据每个数字中连续数字的数量来过滤文件。
示例列表形成其中一个文件:
输入文件data.log
:
12365
91738
349874
128152639
1234
7654
08767
1234567
我想:
1-按包含两个连续数字的数字过滤此列表,预期输出应为:
12365
349874
128152639
1234
7654
08767
1234567
2-按包含三个连续数字的数字过滤此列表,预期输出应为:
12365
349874
1234
7654
08767
1234567
3-按包含四个连续数字的数字过滤此列表,预期输出应为:
1234
7654
1234567
4-按包含五个连续数字的数字过滤此列表,预期输出应为:
1234567
如果数字中连续数字的顺序从小到大(例如 1234 ...等)或从大到小(例如 54321),则应包含在输出中。
答案1
使用grep
、tee
、 和rev
,创建一个充满bash
-isms 的棘手小函数:
dqs() { a=${2:-123456789} ; [ "$1" -ge 2 ] &&
grep -iF "$(eval eval printf '%s\\\\n' \\$\\{a:\{0..$((${#a}-$1))\}:$1\\} |
tee >(rev) )"
}
测试一下:
dqs 5 < data.log
1234567
dqs 4 < data.log
1234
7654
1234567
dqs 3 < data.log
12365
349874
1234
7654
08767
1234567
怎么运行的:
printf
打印所需长度的序列列表,(例如123,234,ETC。),tee
附加一个镜像(IE从右到左或向后)使用 进行复制rev
,然后grep -f <(...)
在标准输入中搜索该列表中的任何内容。
要制作该序列列表通常需要一个循环,或者seq
,甚至两者都需要,但在这里我们通过使用bash
序列表达,结合一个子串扩展, 还有一些算术。但这是不可能的,因为bash
解释器无法按所需的顺序执行这些操作。因此eval eval
,我们使用了一些策略\\\
来强制bash
按正确的顺序做事。
这里[ "$@" -gt 0 ] &&
在功能上不是必需的,但拥有它更安全。它确保dqs
有一个且唯一一个数字参数,否则grep
不会运行。这会阻止eval eval
执行任何操作邪恶的。
奖励:添加第二个参数可以将 更改123456789
为任何其他序列,并且代码仍然可以工作。例如,dqs 4 123456789ABCDEF
将搜索四位十六进制序列(和反向序列),并dqs 3 $(printf %s {a..z})
搜索三字母字母序列。
# search `man bash` for the three most popular words
# that have 3 three char alphabetic runs
man bash | tr ' ' '\n' | sort | uniq -c | sort -gr |
dqs 3 $(printf '%s' {a..z}) | head -3
输出:
92 first
76 default
38 environment
答案2
如果你有很多非常大的文件,awk 中的正则表达式匹配会很慢。一种方法是利用 grep 来完成硬提升,并利用 awk 来构建要搜索的字符串列表(因为您不想对这些进行硬编码)。 IE
$grep -E '12|98|23|87|34|76|45|65|56|54|67|43|78|32|89|21' data.log
可以对两个角色执行此操作,但我们希望能够对最多 9 个字符执行此操作。您需要 -E 来扩展 grep 以支持搜索多个模式(12|98 是两种模式) - 普通的 ol' grep 不允许您这样做。
awk 可以循环字符串 123456789 拉出连续的片段,但我们想要前进和后退,所以:
$awk 'BEGIN {f=123456789 ; b=987654321 ; for(i=1;i<9;i++) print substr(f,i,2),substr(b,i,2)}'
12 98
23 87
34 76
45 65
56 54
67 43
78 32
89 21
让我们添加一些内容,这样长度就不会被硬编码为 2(-vn=3 在 awk 脚本内设置变量 n=3):
$awk -vn=3 'BEGIN {f=123456789 ; b=987654321 ; for(i=1;i<11-n;i++) print substr(f,i,n),substr(b,i,n)}'
123 987
234 876
345 765
456 654
567 543
678 432
789 321
并且(几乎就在那里!)通过将输出记录分隔符(ORS)和输出字段分隔符(OFS)更改为 | 来获得管道符号 grep -E 想要的
$awk -vn=3 'BEGIN {ORS="|" ; OFS="|" ; f=123456789 ; b=987654321 ; for(i=1;i<11-n;i++) print substr(f,i,n),substr(b,i,n)}'
123|987|234|876|345|765|456|654|567|543|678|432|789|321|
我们必须去掉 321 之后的最后一个管道,否则 grep 将匹配所有内容,因此添加 sed '.$//' 以将字符串末尾 ($) 之前的最后一个字符替换为空:
$awk -vn=3 'BEGIN {ORS="|" ; OFS="|" ; f=123456789 ; b=987654321 ; for(i=1;i<11-n;i++) print substr(f,i,n),substr(b,i,n)}' | sed 's/.$//'
现在我们可以将它放在一个 shell 脚本中,这样我们就可以进行一般搜索:
$cat t.sh
#!/bin/bash
grep -E `awk --assign n=$1 'BEGIN {OFS="|" ; ORS="|" ; f=123456789 ; b=987654321 ; for(i=1;i<11-n;i++) print substr(f,i,n),substr(b,i,n)}' | sed 's/.$//'` $2
$chmod 775 t.sh
$./t.sh 4 data.log
1234
7654
1234567
答案3
许多大文件表明这需要快速完成。这意味着一个while read
循环是不可能的。这里要认识到的一件事是,每个练习都可以简化为(至少)匹配一小组模式中的一个,并且可以这样做真的快速使用grep
或类似的工具,如rg
或ack
。例如,对于五位数字序列:
grep -e 12345 -e 23456 […] -e 65432 -e 54321
请参阅man grep
获取更多信息,并使用格雷格的维基快速学习 Bash。