在 Linux 中打印每个单词包含两个相同字符的行

在 Linux 中打印每个单词包含两个相同字符的行

我有这样的输入

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

我正在尝试打印每个单词包含的行至少有两个相同的字符,使用 grepcommand 最长的一行包含 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

或者使用grep支持类似 perl 正则表达式的实现:

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

打印出的行不是(with -v) 包含一个\S(非空白字符),该字符前面没有另一个非空白 ( (?<!\S)) (或 IOW 是空白分隔单词的开头),并且不是一系列非空白字符的开头其中重复的是( (?!\S*(\S)\S*\1))。因此本质上类似于(尽管不太清晰)perl上述方法。

请注意,它们还打印空行(因为它们不包含没有重复字符的单词)。如果您不需要它们,您可以排除它们,这应该是微不足道的(例如通过-e '^\s*$'在其中添加 a grep)。

答案2

在每个 Unix 机器上的任何 shell 中使用任何 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与模块all中的方法一起使用List::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 个部分 => 在该单词中检测到 dup。同样,如果检测到的重复项计数等于字段数 => 适合打印的行,则在当前行末尾。

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

相关内容