过滤包含连续数字的数字列表

过滤包含连续数字的数字列表

我想过滤许多文本文件。每个文件都包含一个很长的数字列表。必须根据每个数字中连续数字的数量来过滤文件。

示例列表形成其中一个文件:

输入文件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

使用greptee、 和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或类似的工具,如rgack。例如,对于五位数字序列:

grep -e 12345 -e 23456 […] -e 65432 -e 54321

请参阅man grep获取更多信息,并使用格雷格的维基快速学习 Bash。

相关内容