模式匹配排除重复字符

模式匹配排除重复字符

是否有一个正则表达式可以匹配字符集中的字符但只匹配一次?换句话说,一旦找到一个字符,就将其从集合中删除。

如果 grep 不能做到这一点,是否有内置实用程序可以做到这一点?

例子:

Characters to match only once:   spine

输入:

spine
spines
spin
pine
seep 
spins

输出:

spine
spin
pine

编辑:
有很多方法可以实现此输出(下面是一个示例),但我正在寻找一种方法来实现此目的,而无需为我想要匹配的每个模式自定义命令。

grep '[spine]' input_file | grep -v 's.*s' | ... | grep -v 'e.*e'

答案1

常用表达从数学意义上来说,这是可能的,但是正则表达式的大小相对于字母表的大小呈指数增长,因此不切实际。

有一个简单的方法,用否定和反向引用

grep '[spine]' | grep -Ev '([spine]).*\1'

第一个grep选择包含至少一个的行einps;第二个grep拒绝包含超过一个的行(例如允许spinal tapandspend但不允许foobaror see)。

答案2

受你的表达的启发,我可以使用egrep想出一个更短的表达:

egrep -v '(s.*s|p.*p|i.*i|n.*n|e.*e)' FILE

这相当于

sed /s.*s/d;/p.*p/d;/i.*i/d;/n.*n/d;/e.*e/d; FILE

这是如何从输入自动生成 sed 命令:

#!/bin/bash
word=$1
file=$2
expr=$(for c in $(echo $word | sed 's/./& /g'); do echo -n "/"$c".*"$c"/d;"; done);
sed $expr $file 

我尝试了使用 grep 的类似方法,但无法说服 shell 从变量中获取 grep 模式,但如果我回显它,并通过剪切和粘贴插入结果,则该命令有效:

expr="'("$(for c in $(echo $wort | sed 's/./& /g'); do echo -n $c".*"$c"|"; done)

egrep -v ${expr/%|/)\'} FILE
# doesn't work, filters nothing, whole file is printed
# check:    
echo egrep -v $(echo $exp) FILE 
egrep -v '(s.*s|p.*p|i.*i|n.*n|e.*e)' FILE
# manually: 
egrep -v '(s.*s|p.*p|i.*i|n.*n|e.*e)' FILE
spine
spin
pine

也许我犯了一个错误,也许我在变量扩展方面犯了错误。

答案3

这是一种非正则表达式的方法,无需提前知道字符串是什么。并不是说这是最有效的,但它足够快以满足我的需求。

$ (echo a;echo abc;echo aabc;echo def;echo two words;echo one pair) | awk '
>   {
>     split($0,a,"");
>     n=asort(a);
>     for(i=1;i<=n;i++){
>       if(a[i]==a[i+1]){
>         next
>       }
>     }
>   }
>   n'
a
abc
def
one pair

其作用是将每一行分割$0成一个数组a,然后对该数组进行排序,返回n数组的长度。然后,它遍历数组,如果排序数组中的两个相邻字符相同,则退出到下一个单词。如果它一直通过单词,它将打印(整个)输入行。请注意,由于空格重复,三个单词或更多单词的行始终无法打印。

示例 - 查找没有重复字符的所有五个字母单词:

$ grep '^.....$' /usr/share/dict/words | tr '[A-Z]' '[a-z]' | awk '{split($1,a,"");n=asort(a);for(i=1;i<=n;i++){if(a[i]==a[i+1]){next}}}n' | head -5
abhor
abide
abies
abilo
abler

相关内容