Dyl*_*eus 23 grep regular-expression
我正在尝试编写一个正则表达式,它将显示所有 10 个字符长的单词,并且没有一个字母重复。
到目前为止,我有
grep --colour -Eow '(\w{10})'
Run Code Online (Sandbox Code Playgroud)
这是问题的第一部分。我将如何检查“唯一性”?我真的不知道,除此之外我需要使用反向引用。
Sté*_*las 41
grep -Eow '\w{10}' | grep -v '\(.\).*\1'
Run Code Online (Sandbox Code Playgroud)
排除具有两个相同字符的单词。
grep -Eow '\w{10}' | grep -v '\(.\)\1'
Run Code Online (Sandbox Code Playgroud)
排除具有重复字符的那些。
POSIXly:
tr -cs '[:alnum:]_' '[\n*]' |
grep -xE '.{10}' |
grep -v '\(.\).*\1'
Run Code Online (Sandbox Code Playgroud)
tr
通过将任何s非单词字符c序列(字母数字和下划线的补充)转换为换行符,将单词放在自己的行上。
或者用一个grep
:
tr -cs '[:alnum:]_' '[\n*]' |
grep -ve '^.\{0,9\}$' -e '.\{11\}' -e '\(.\).*\1'
Run Code Online (Sandbox Code Playgroud)
(不包括少于 10 个和超过 10 个字符的行以及那些字符至少出现两次的行)。
grep
只有一个(带有 PCRE 支持的 GNU grep 或pcregrep
):
grep -Po '\b(?:(\w)(?!\w*\1)){10}\b'
Run Code Online (Sandbox Code Playgroud)
即,字边界 ( \b
) 后跟 10 个字字符序列(前提是每个字边界后不跟有字字符序列和它们自己,使用负先行 PCRE 运算符(?!...)
)。
我们很幸运它在这里工作,因为没有多少正则表达式引擎在重复部分内使用反向引用。
请注意(至少使用我的 GNU grep 版本)
grep -Pow '(?:(\w)(?!\w*\1)){10}'
Run Code Online (Sandbox Code Playgroud)
不起作用,但是
grep -Pow '(?:(\w)(?!\w*\2)){10}'
Run Code Online (Sandbox Code Playgroud)
do (as echo aa | grep -Pw '(.)\2'
) 这听起来像一个错误。
你可能想要:
grep -Po '(*UCP)\b(?:(\w)(?!\w*\1)){10}\b'
Run Code Online (Sandbox Code Playgroud)
如果您想\w
或\b
将任何字母视为单词组件,而不仅仅是非 ASCII 语言环境中的 ASCII 字母。
另一种选择:
grep -Po '\b(?!\w*(\w)\w*\1)\w{10}\b'
Run Code Online (Sandbox Code Playgroud)
这是一个单词边界(后面没有一个单词字符序列,其中一个重复的单词字符)后面是 10 个单词字符。
一个人可能会想到的事情:
Babylonish
例如将被匹配,因为即使有两个B
s,一个小写和一个大写(用于-i
更改它),所有字符都是不同的。-w
, \w
and \b
,一个单词是一个字母(ASCII 字符grep
目前仅适用于 GNU ,[:alpha:]
如果使用-P
and则是您的语言环境中的字符类(*UCP)
)、十进制数字或下划线。c'est
(根据单词的法语定义的两个单词)或it's
(根据单词的某些英语定义的单词)或rendez-vous
(根据单词的法语定义的单词)不被视为一个单词。(*UCP)
,Unicode 组合字符也不被视为单词组件,因此téle?phone
( $'t\u00e9le\u0301phone'
) 被视为 10 个字符,其中一个是非字母字符。défavorise?
( $'d\u00e9favorise\u0301'
) 会匹配,即使它有两个,é
因为那是 10 个所有不同的字母字符,后跟一个组合重音符号(非字母,所以在e
和它的重音之间有一个单词边界)。gol*_*cks 13
好的......这是一个五个字符的字符串的笨拙方式:
grep -P '^(.)(?!\1)(.)(?!\1|\2)(.)(?!\1|\2|\3)(.)(?!\1|\2|\3|\4).$'
Run Code Online (Sandbox Code Playgroud)
因为您不能在字符类(例如[^\1|\2]
)中放置反向引用,所以您必须使用否定前瞻-- (?!foo)
。这是 PCRE 功能,因此您需要-P
切换。
当然,一个 10 个字符的字符串的模式会长很多,但是有一个更短的方法,它在前瞻中使用可变长度的任何匹配('.*'):
grep -P '^(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!.*\4)(.)(?!.*\5).$'
Run Code Online (Sandbox Code Playgroud)
在阅读了 Stephane Chazelas 的启发性答案后,我意识到通过 grep 的-v
开关可以使用类似的简单模式:
(.).*\1
Run Code Online (Sandbox Code Playgroud)
由于检查一次一个字符,这将查看任何给定字符后面是否跟有零个或多个字符 ( .*
),然后是反向引用的匹配项。 -v
反转,仅打印与此模式不匹配的内容。这使得反向引用更有用,因为它们不能被字符类否定,并且显着:
grep -v '\(.\).*\1'
Run Code Online (Sandbox Code Playgroud)
将用于识别具有唯一字符的任意长度的字符串,而:
grep -P '(.)(?!.*\1)'
Run Code Online (Sandbox Code Playgroud)
不会,因为它将匹配具有唯一字符的任何后缀(例如abcabc
匹配因为abc
在最后,aaaa
因为a
在最后——因此是任何字符串)。这是由零宽度环视(它们不消耗任何东西)引起的复杂情况。
如果您不需要在正则表达式中完成所有操作,我会分两步完成:首先匹配所有 10 个字母的单词,然后过滤它们以获得唯一性。我知道如何做到这一点的最短方法是在 Perl 中:
perl -nle 'MATCH:while(/\W(\w{10})\W/g){
undef %seen;
for(split//,$1){next MATCH if ++$seen{$_} > 1}
print
}' your_file
Run Code Online (Sandbox Code Playgroud)
请注意额外的\W
锚点,以确保仅匹配长度正好为 10 个字符的单词。