Linux で各単語に同じ文字が 2 つ含まれる行を出力する

Linux で各単語に同じ文字が 2 つ含まれる行を出力する

私はこのように入力しました

LTCYMM SVNNDA DTVEV QLOPGO CUPUR
MMWVJM LIVLI WBSVD UQCMW HBMDA HVVFY BWYSS
NOGWOS JIKKDI GCIQAD MXJNWE SMVFCB GIZVPA GZOHZR WJBMZS
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS
VYQQC BFAWI NSZDV HKPGI KVJOC COPPS
JGU YLN MXW ACR BZA HOP
TMCVPT HBNGIH IQYGCI DTQPON WXANKG GMIYZS
CWVT BUBA NSGR MUPO LDNS

各単語に含まれる行を印刷しようとしています少なくとも2つの同じ文字grepを使用すると、command 最長の行には8つの単語が含まれます。このようにして解決できると思いますが、それは間違った方法だと感じています。

grep '^.*\([A-Z]\)[^ ]*\1[^ ]* [^ ]*\([A-Z]\)[^ ]*\2[^ ]*   [^ ]*\([A-Z]\)[^ ]*\3[^ ]* [^ ]*\([A-Z]\)[^ ]*\4[^ ]* [^ ]*\([A-Z]\)[^ ]*\5[^ ]* [^ ]*\([A-Z]\)[^ ]*\6[^ ]* [^ ]*\([A-Z]\)[^ ]*\7[^ ]* [^ ]*\([A-Z]\)[^ ]*\8[^ ]*$/| .... for 7 words | for 6 ...

期待される出力

 LTCYMM SVNNDA DTVEV QLOPGO CUPUR
 KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS

答え1

perl

$ perl -ne 'print unless grep {!/(.).*\1/} /\S+/g' file
LTCYMM SVNNDA DTVEV QLOPGO CUPUR
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS

または、grepPerl のような正規表現をサポートする実装の場合:

$ grep -Pve '(?<!\S)(?!\S*(\S)\S*\1)\S' file
LTCYMM SVNNDA DTVEV QLOPGO CUPUR
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS

これは、ない( -v)には\S、前に別の非空白文字 ( (?<!\S)) が付いていない (つまり、空白で区切られた単語の始まり) (非空白文字) が含まれており、その 1 つが繰り返される非空白文字のシーケンスの始まり ( (?!\S*(\S)\S*\1)) ではありません。したがって、本質的には上記のアプローチに似ています (ただし、読みやすさは劣ります) perl

空白行も出力されることに注意してください (空白行には繰り返し文字のない単語は含まれないため)。空白行が不要な場合は、簡単に除外できます (たとえば、 1 行-e '^\s*$'目にa を追加するなどgrep)。

答え2

あらゆる Unix ボックス上のあらゆるシェルで awk を使用する:

awk '{
    for ( fldNr=1; fldNr<=NF; fldNr++ ) {
        numChars = length($fldNr)
        numUnq = 0
        split("",seen)       # you could use delete(seen) here in most awks
        for ( charNr=1; charNr<=numChars; charNr++ ) {
            if ( !seen[substr($fldNr,charNr,1)]++ ) {
                numUnq++
            }
        }
        if ( numUnq == numChars ) {
            next
        }
    }
    print 
}' file
LTCYMM SVNNDA DTVEV QLOPGO CUPUR
KKPQBP BKDKRU ZTPDPL ZRLUEL HRZZKO KXSKCU YZQTBT RISNKS

答え3

モジュールのメソッドperlと一緒に使用することで、必要な行(少なくとも1つの重複した文字を含むすべての単語)を検出できます。allList::Util

perl -MList::Util=all  -lane '
  print if all { /(.).*\1/ } @F;
' file

を使用すると、GnU sed必要なすべてのフィールドが行の先頭から末尾まで伸びていることを確認してから、必要な行を選択できます。

$ sed -En '/^\s*(\S*(\S)\S*\2\S*(\s+|$))+$/p' file

別の方法としては、sed非空白文字間で重複した文字を段階的にチェックし、非空白文字の実行中に重複が見つからなくなったらすぐにパターン スペースを印刷しないようにします。

sed -Ee 'h
  :loop
    s/^\s+|\s+$//g
    s/\S+/&\n/
    /(\S).*\1.*\n/!d
    s/^[^\n]*\n//
  /./bloop
  g
' file

awk を利用して、すべての単語と単語内のすべての文字をループします。文字ごとに単語を分割し、2 つ以上の部分に分割されるかどうかを確認します => この単語で重複が検出されます。同様に、現在の行の末尾で検出された重複の数がフィールドの数と等しい場合は => 行を印刷に適合させます。

awk '
{
  for (p=i=1+(w=0); i<=NF; i++) {
    while (p <= length($i)) {
      c = substr($i,p++,1)
      if (split($i,a,c) > 2) {
        w += p = 1
        break
      }
    }
  }
}
w==NF
' file

答え4

純粋な Bash での別の解決策がここにあります — いいえperl、いいえgrep、いいえawk

#!/bin/bash
set -euo pipefail

containssametwice() {
  local -Ai chars=()
  local -i i
  for ((i = 0; i < ${#1}; ++i)); do
    ((++chars["${1:i:1}"] < 2)) || return 0
  done
  return 1
}

while IFS= read -r line; do
  read -ra words <<< "$line"
  for word in "${words[@]}"; do
    containssametwice "$word" || continue 2
  done
  printf '%s\n' "$line"
done

関連情報