检查一行是否至少有 2 个不同的字母

1 text-processing

我有一个文本文件,每行都有一个单词。我正在尝试删除至少没有两个不同字母的行。例如,文件看起来像这样:

words
books
aaa
letters 
zzzz
Run Code Online (Sandbox Code Playgroud)

我希望输出文件看起来像这样:

words
books
letters
Run Code Online (Sandbox Code Playgroud)

我尝试将每个单词分解为单独的字母,而不是通过使用uniq -c然后将它们组合在一起,wc -l但卡在了 if 语句上。也相信必须有一种更简单的方法来做到这一点,我只是想不出任何其他方法来解决这个问题。

Sté*_*las 7

假设您的意思是字符而不是字母(例如,您还想删除包含...11即使.1不是字母的行):

grep -vx -e '' -e '\(.\)\1*'
Run Code Online (Sandbox Code Playgroud)

或者:

grep -vx '\(\(.\)\2*\)\{0,1\}'
Run Code Online (Sandbox Code Playgroud)

即删除 ( -v) 空行或以一个字符 ( .) 开头的行,后跟同一个字符(\1作为对所捕获内容的反向引用\(...\))重复 0 次或更多次 ( *) 直到行尾(-x将模式锚定在行的开始和结束)。

可移植地,您不能使用egreporgrep -E在这里,因为标准 ERE 没有反向引用(只有 BRE 有)。

对于包含至少两个不同字母的行,忽略其他类型的字符(我们将[[:alpha:]]在此处用于letter,即在您的语言环境中被视为字母的任何字符):

grep -vx '[^[:alpha:]]*
[^[:alpha:]]*\([[:alpha:]]\)\([^[:alpha:]]*\1\)*[^[:alpha:]]*'
Run Code Online (Sandbox Code Playgroud)

(在两行上,这是传递两种不同模式的另一种方式)。或者:

grep -vx '[^[:alpha:]]*\([^[:alpha:]]*\([[:alpha:]]\)\([^[:alpha:]]*\2\)*[^[:alpha:]]*\)\{0,1\}'
Run Code Online (Sandbox Code Playgroud)

那个会删除像12345aaa(只有一个字母)或-+-+-+-(没有字母)这样的行。

如果您还想删除Aaaa行(即比较字母时忽略大小写),请添加该-i选项。

请注意,它在字符级别工作,因此如果有多个字符表示的字素,它可能无法达到您的预期。例如,它会通过以下方式删除与该输出类似的行:

 $ printf 'e\u0300e\u0301\n'
 e?e?
Run Code Online (Sandbox Code Playgroud)

(假设 GNUprintf或兼容),但不是像:

 $ printf '\ue8\ue9\n'
 èé
Run Code Online (Sandbox Code Playgroud)

(其中e\u300是字素的分解形式和\ue8预组合形式èe(U+0065) 和è(U+00E8) 是按字母顺序排列的,但不是 U+0300 或 U+0301 组合严重/尖锐的口音)。

要使用字素,您可以使用pcregrep或 GNUgrep及其-P选项:

对于第一种情况(至少两个不同的字素簇):

grep -vxP '(?:(\X)\1*)?'
Run Code Online (Sandbox Code Playgroud)

对于第二种情况(至少两个不同的字母字形簇):

grep -vxP '(?:(?=\PL)\X)*(?:((?=\pL)\X)(?:(?:(?=\PL)\X)*\1(?!\pM))*(?:(?=\PL)\X)*)?'
Run Code Online (Sandbox Code Playgroud)

哪里(?=\PL)\X是非字母字形簇(字形簇 ( \X),前提是(?=...)它以非字母 ( \PL) 和(?=\pL)\X字母字形簇开头。

\pL正确匹配字母unicode。与[:alpha:]POSIX 字符类相反,它还包括来自非字母脚本的字母。

请注意,它会将e\u300\u301, e\u301\u300, \ue9\u300,\ue8\u301视为四个不同的簇,即使它们e都是带有锐音和重音的 a。

还要注意像?(U+FB03) 这样在一个字符中包含多个字母的字符


使用 PCRE,您还可以采取积极的方法:

  • 至少 2 个不同的字符:

    grep -P '(.).*(?!\1).'
    
    Run Code Online (Sandbox Code Playgroud)
  • 至少 2 个不同的字母字符:

    grep -P '(\pL).*(?!\1)\pL'
    
    Run Code Online (Sandbox Code Playgroud)
  • 至少 2 个不同的字形簇:

    grep -P '^\X*(\X)\X*(?!\1(?!\pM))\X'
    
    Run Code Online (Sandbox Code Playgroud)

    一个人不能正常使用分解形式的古兰经韩文(至少)。PCRE(与perl的 RE相反\b{g})没有字素边界运算符 (AFAIK),并且对 unicode 属性的支持有限。我们使用(?!\pM)(这意味着在这种情况下:“前提是后面没有组合标记字符”)作为近似值,但这不适用于多部分韩文字母/音节字符,其中部分没有财产。????????例如,它会删除。现在人们也可能会争辩说,每个部分都是一个不同的字母......

    使用perl5.22 或更高版本,您可以编写它:

    perl -Mopen=locale -lne 'print if /\b{g}(\X).*\b{g}(?!\1\b{g})\X/'
    
    Run Code Online (Sandbox Code Playgroud)
  • 至少 2 个不同的字母字形簇:

    grep -P '^\X*((?=\pL)\X)\X*(?!\1(?!\pM))(?=\pL)\X'
    
    Run Code Online (Sandbox Code Playgroud)

    同样,不适用于????????. 与perl

    perl -Mopen=locale -lne 'print if /\b{g}(?=\pL)(\X).*\b{g}(?!\1\b{g})(?=\pL)\X/'
    
    Run Code Online (Sandbox Code Playgroud)

使用perl,我们可以使用更直接的方法,例如: