web-dev-qa-db-ja.com

パターンマッチングは重複する文字を除外します

文字セット内の文字に一致するが1回だけである次の正規表現はありますか?つまり、文字が見つかったら、その文字をセットから削除します。

Grepがこれを実行できない場合、実行できる組み込みユーティリティはありますか?

例:

Characters to match only once:   spine

入力:

spine
spines
spin
pine
seep 
spins

出力:

spine
spin
pine

編集:
この出力を実現する方法はたくさんありますが(以下の1つの例)、一致させたいパターンごとにコマンドをカスタマイズすることなく、これを実現する方法を探しています。

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

7
Steven

正規表現 数学的な意味では可能ですが、正規表現のサイズはアルファベットのサイズに比べて指数関数的に大きくなるため、実用的ではありません。

否定と backreferences を使用する簡単な方法があります。

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

最初のgrepは、einpsの少なくとも1つを含む行を選択します。 2番目のgrepは、複数の行を含む行を拒否します(たとえば、spinal tapおよびspendですが、foobarまたはseeではありません)。

あなたの表現に触発されて、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-commandを自動的に生成する方法です。

#!/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で同様のアプローチを試しましたが、変数からgrep-patternを取得するようにシェルを説得できませんでしたが、エコーアウトして結果をカットアンドペーストで挿入すると、コマンドは機能しました。

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

たぶん私は間違いを犯したのかもしれませんし、変数の展開を間違えたのかもしれません。

1
user unknown