grep 命令,该命令将行与组中每个字符的 2 个字符进行匹配

grep command that matches lines with exactly 2 of every character from a group

提问人:mister entername 提问时间:10/22/2023 最后编辑:mister entername 更新时间:10/28/2023 访问量:220

问:

我需要帮助找出一个带有 grep 的正则表达式(我只能使用 grep),该表达式将搜索文件并显示包含 [a-f0-9] 中每个字符的 2 行。

有效匹配示例:

33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a

无效匹配示例:

c0 b0 f5 60 02 8b 1c a4 41 7c 53 f2 85 20 a0 d1

我试过了,但它显然行不通,因为你不能否定这样的反向引用,即使我纠正了它,我也不确定这是正确的方法。'(?:[0-9a-f])?^[^\1]*\1[^\1]*\1[^\1]*$'

正则表达式 grep

评论

0赞 Benjamin W. 10/22/2023
所有行是否都看起来像这样(16 组,每组 2 个十六进制字符),还是正则表达式也必须过滤完全不同的行?
0赞 mandy8055 10/22/2023
无效匹配的模式是什么?
1赞 bobble bubble 10/22/2023
使用另一个想法:^(?!.*?(\S).*\1.*\1)(?:\b ?[a-f\d]{2}){16}$ 它与 @jhnc 的解决方案之一非常相似,所以我不会发布另一个答案。grep -P
1赞 jhnc 10/22/2023
@bobblebubble我认为如果成为它会失败得更快。锚定和完全懒惰也应该有所帮助.请记住设置 C/POSIX 语言环境(例如),否则可能会匹配超出预期的内容\S[a-f\d]((?!^.*?([a-f\d]).*?\1.*?\1)LANG=C\d
1赞 Cary Swoveland 10/25/2023
在上面的评论中,您被问到了几个问题,但您没有回答任何问题。这是吸引反对票和关闭票的可靠方法。我对你对@Benjamin两天前提出的问题的回答特别感兴趣。在评论中回答问题时,通常最好编辑您的问题,而不是回复评论。这将使所有读者都更清楚地了解这个问题,而不仅仅是那些阅读所有评论的人。

答:

0赞 kabirfaisal 10/22/2023 #1

下面是一个 Perl 单行代码的例子,它应该可以解决问题:

perl -ne 'print if /^[a-f0-9]*$/ && !grep {($_ =~ tr/$_//) != 2} 0..9,"a".."f"' file.txt

此命令执行以下操作:

  • perl -ne:为输入中的每一行运行以下 Perl 代码。
  • /^[a-f0-9]*$/:检查该行是否仅包含范围内的字符。[a-f0-9]
  • !grep {($_ =~ tr/$_//) != 2} 0..9,"a".."f":对于十六进制范围内的每个字符,计算行 () 中的出现次数。如果计数不正好是 2,则字符通过 grep 过滤器。运算符反转 grep 结果,因此仅当所有字符的计数正好为 2 时,grep 表达式才为 true。[a-f0-9]$_ =~ tr/$_//!
  • print if:如果 grep 表达式为 true,则打印该行。

此命令打印仅由十六进制字符组成且每个十六进制字符正好出现两次的所有行。file.txt

评论

2赞 Benjamin W. 10/22/2023
这与我的有效输入示例不匹配?
0赞 mister entername 10/22/2023
对我不起作用,我也忘了提到我只能使用 grep
0赞 kabirfaisal 10/22/2023
我很抱歉我更新了评论
1赞 Ed Morton 10/22/2023
为我产生任何输出。
0赞 jhnc 10/23/2023
$_不插值 。 说:“从来没有任何变量插值,所以”$“和”@“总是被视为文字”tr/$_//perldoc -f y
2赞 Dimava 10/22/2023 #2

你可以用一种直接的方式做到这一点

^                           start of line (check docs)
(?=(\S\S\s){15}\S\S$)       matches 32 symbols with spaces
(?=[^1]*1[^1]*1[^1]*$)      contains exactly two ones
(?=[^2]*2[^2]*2[^2]*$)      contains exactly two ones
...
(?=[^f]*f[^f]*f[^f]*$)      contains exactly two Fs
2赞 jhnc 10/22/2023 #3

使用任何 POSIX grep

grep -vEf regexlist filetosearch

其中包含:regexlist

[^0-9a-f ]
^ *([0-9a-f] *){0,31}$
^ *([0-9a-f] *){33}
(0.*){3}
(1.*){3}
(2.*){3}
(3.*){3}
(4.*){3}
(5.*){3}
(6.*){3}
(7.*){3}
(8.*){3}
(9.*){3}
(a.*){3}
(b.*){3}
(c.*){3}
(d.*){3}
(e.*){3}
(f.*){3}
  • 只能包含十六进制和空格
  • 必须包含不少于且不超过 32 个十六进制
  • 不能包含任何十六进制的三个(否则其他十六进制将少于两个)

这是利用德摩根定律

( A & B & C & ...)== NOT( NOT(A) |非(B) |NOT(C) |...)

我们提供了一个必须失败的正则表达式的 OR 列表,然后使用 grep 进行反转,从而产生一个必须匹配的正则表达式的 AND 列表。-v


如果十六进制必须成对出现,请添加:

^[^ ] +
 [^ ] +
 [^ ]$
[^ ]{3}

对于每个数字之间只有一个空格,并且在行首或行尾没有空格:

 {2}
^ +
 +$

如果你的支持,那就更简单了。例如:grep-P

grep -P '^(?!^.*?([0-9a-f]).*\1.*\1)(?!^.*?[^0-9a-f ])(?=[^ ]{2}( [^ ]{2}){15}$).*$' filetosearch
  • ^... - 匹配整行.*$
  • (?!^.*?([0-9a-f]).*?\1.*?\1)- 不能包含三个(或更多)
  • (?!^.*?[^0-9a-f ])- 只有十六进制和空格
  • (?=[^ ]{2}( [^ ]{2}){15}$)- 正好 16 对

或者,如果空格的数量无关紧要,甚至:

grep -P '^(?!^.*?([0-9a-f]).*?\1.*?\1)( *[0-9a-f]){32} *$' filetosearch

我相信锚定和懒惰:

^(?!^.*?([0-9a-f]).*?\1.*?\1).*$

匹配(即失败)应该比未锚定和/或贪婪更快:

^(?!.*?([0-9a-f]).*?\1.*?\1).*$
^(?!.*([0-9a-f]).*\1.*\1).*$
^(?!^.*([0-9a-f]).*\1.*\1).*$
2赞 CAustin 10/22/2023 #4

我认为这是最简单的方法:

^(?!.*([a-f0-9])(?:.*\1){2,})(?:[a-f0-9]{2}(?: |$)){16}$

基本上,它断言行的开头后面没有出现 3 个或更多个相同的字符,然后匹配以空格分隔的字符对。

https://regex101.com/r/zerktu/1

评论

0赞 jhnc 10/22/2023
这失败了:例如,不应该匹配,但确实如此echo 00 11 22 | grep -P '^(?!.*([a-f0-9])(?:.*\1){2,})(?:[a-f0-9]{2}(?: |$))+$'
0赞 CAustin 10/22/2023
@jhnc 据我了解,OP 只想排除具有 3 个或更多相同字符的字符串,而没有,因此它应该是有效的匹配。00 11 22
0赞 jhnc 10/22/2023
不是根据标题或第一句话:“正好两个”/“包括 [A-F0-9] 中每个字符的 2 个”。注意也匹配,绝对不包含任何内容中的两个01 23 45
0赞 CAustin 10/22/2023
他们说“一个小组中每个角色正好是两个”,而不是“每个小组正好两个”。
1赞 CAustin 10/22/2023
啊,我没有意识到你是说字符串需要详尽地包含字符集中的所有内容。在这种情况下,可以通过用 而不是 量化最后一部分来修复模式,因为越少,我们就会错过一些东西,再多一点就会给我们 3+ 份的东西。感谢您指出这一点!{16}+
1赞 Ed Morton 10/22/2023 #5

我知道 OP 出于某种原因需要 grep 解决方案,但其他将来阅读此问题时遇到类似问题的人可能没有同样的限制。

给定此输入,其中第一行有效,但最后 3 行涵盖了我认为的 3 种可能的故障情况:(1) 输入包含的 [0-9a-f 中的一个字符少于 2 个],或 2) 输入包含不在 [0-9a-f] 中的字符,或 3) 输入包含 [0-9a-f] 中其中一个字符的 2 个以上):

$ cat file
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a
   e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a x
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a 3

并牢记这句话

有些人在遇到问题时会想“我知道,我会用 正则表达式。现在他们有两个问题。

这是我真正处理这个问题的方式,在每个 Unix 机器上的任何 shell 中使用任何 awk:

$ awk '
    BEGIN { chars="abcdef0123456789"; lgth=length(chars) }
    {
        input = $0
        for (i=1; i<=lgth; i++) {
            char = substr(chars,i,1)
            if ( gsub(char,"",input) != 2 ) {
                next
            }
        }
    }
    input ~ /^ *$/
' file
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a
1赞 Paul Hodges 10/28/2023 #6

如果您只能使用...
那么我认为管道会让它更容易阅读和维护,尽管我不喜欢这个设计运行 18 次的事实......
grepgrep

$: cat file
c8 f1 7a d9 f2 a7 a0 5a e9 9f c1 4c 2f e3 f5 3b |aanbestedingsdossier|
56 fc 9f 14 da 80 51 3e 74 9a 73 ed 6b c2 80 2b |aanbetaalt|
ce ba ed a0 a5 27 fd 4c 22 a2 1d a1 87 46 91 b3 |aanbranden|
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a |good example|
c0 b0 f5 60 02 8b 1c a4 41 7c 53 f2 85 20 a0 d1 |bad example|

$: grep -E '([[:alnum:]]).*\1.* \|' file              | # lines with at least 2 occurances
  grep -vE '([[:alnum:]]).*\1.*\1.* \|'               | # minus lines with 3
  grep a | grep b | grep c | grep d | grep e | grep f | # now just assert each required character
  grep 0 | grep 1 | grep 2 | grep 3 | grep 4 | grep 5 | grep 6 | grep 7 | grep 8 | grep 9
56 fc 9f 14 da 80 51 3e 74 9a 73 ed 6b c2 80 2b |aanbetaalt|
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a |good example|

当然,您至少可以一次性完成所有操作吗?sed


$: sed '
  /\([[:alnum:]]\).*\1.* |/!d;              # drop lines without at least 2 occurances
  /\([[:alnum:]]\).*\1.*\1.* |/d;           # drop lines with 3
  /a/!d; /b/!d; /c/!d; /d/!d; /e/!d; /f/!d; # now drop lines that lack any required character
  /0/!d; /1/!d; /2/!d; /3/!d; /4/!d;
  /5/!d; /6/!d; /7/!d; /8/!d; /9/!d;
' file
56 fc 9f 14 da 80 51 3e 74 9a 73 ed 6b c2 80 2b |aanbetaalt|
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a |good example|

$: sed -E '
  /([[:alnum:]]).*\1.* \|/!d;               # drop lines without at least 2 occurances
  /([[:alnum:]]).*\1.*\1.* \|/d;            # drop lines with 3
  /a/!d; /b/!d; /c/!d; /d/!d; /e/!d; /f/!d; # now drop lines that lack any required character
  /0/!d; /1/!d; /2/!d; /3/!d; /4/!d;
  /5/!d; /6/!d; /7/!d; /8/!d; /9/!d;
' file
56 fc 9f 14 da 80 51 3e 74 9a 73 ed 6b c2 80 2b |aanbetaalt|
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a |good example|

$: sed '
  /\([[:alnum:]]\).*\1.* |/!d
  /\([[:alnum:]]\).*\1.*\1.* |/d
  /a/!d
  /b/!d
  /c/!d
  /d/!d
  /e/!d
  /f/!d
  /0/!d
  /1/!d
  /2/!d
  /3/!d
  /4/!d
  /5/!d
  /6/!d
  /7/!d
  /8/!d
  /9/!d
' file
56 fc 9f 14 da 80 51 3e 74 9a 73 ed 6b c2 80 2b |aanbetaalt|
33 e0 f1 76 9c 4f a8 6c 01 5d 45 9e 28 db 7b 2a |good example|

你可能必须把这些命令中的每一个都放在自己的一行上,但这仍然更好,而且可能更容易让下一个人把他的脑袋绕过来。