如何使用 bash 删除文本文件的镜像行

How to delete mirror lines of a text file using bash

这是文件:

a b
c d
d c
b a

第 1 行与第 4 行镜像,第 2 行与第 3 行镜像。我只想保留其中一条镜像线。这意味着我要删除第 3 行和第 4 行。这只是一个示例。我想要的是有一个代码,计算出镜像线并只保留其中一条。

试试下面的命令: 这仅适用于 GNU awk

awk '{split ([=10=], ln, " "); asort( ln ); for( i = 1; i <= length(ln); i++ ) printf("%s ", ln[i] ); printf( "\n" )}' file.txt | sort -u

说明: 这里我对行进行水平排序,所以:

  • split ($0, line, " ") --> 将行拆分为名为“ln”的数组
  • asort( ln ) --> 将对这个数组进行排序
  • for( i = 1; i <= length(ln); i++ ) printf("%s ", ln[i] ) --> 这个是用来打印输出的,但是会是单行行。
  • printf( "\n" ) --> 这与上面的部分一起使用,使输出行分隔
  • 以上部分将水平排序文件,sort -u 将只保留唯一值。

可能需要永远 运行 -

的快速破解
declare -A item=()
while read -a l
do x="$(printf "%s\n" "${l[@]}"|sort|tr '\n' ' ')"
   item["$x"]=1
done < file
printf "%s\n" "${!item[@]}" | sort
a b
c d

文件越大and/or越复杂,速度越慢,占用的内存也就越多。请改用 Homer 的 awk 解决方案。

另一个 awk:

$ awk '
!((,) in a) && !((,) in a) {    # if neither version is found in hash a
    print                               # output
    a[,]                            # and hash
}' file

输出:

a b
c d

编辑: 另一个反转字符串的版本,可以处理字符串,例如 ab cd vs. dc ba:

awk '
function rev(str,    rts) {        # string reversing function
    for(i=1;i<=length();i++)
        rts=substr(str,i,1) rts
    return rts
}
!([=12=] in a) && !(rev([=12=]) in a) {    # if neither version is found in hash a
    print                          # output
    a[[=12=]]                          # and hash
}' file

首先存储文件。在一个数组中的值,另一个数组中的行号。
运行 再次使用反转线并寻找相等的值。
当反向值在原始集合中时,您知道镜像具有相同的行号。可以从原来的集合中删除镜像,知道刚刚发现反转的值也在集合中。

awk 'NR==FNR{a[[=10=]]=FNR;b[NR]=[=10=]; next}
     [=10=] in a {delete a[b[FNR]]; }
     END { for (i in a) { print i } }' inputfile <(rev inputfile)

举例说明:
当您 运行 使用示例文件时

a b
c d
d c
b a

读取第一个文件后的数组看起来像

a[a b]=1, a[c d]=2, a[d c]=3, a[b a]=4
b[1]=a b, b[2]=c d, b[3]=d c, b[4]=b a

现在处理逆向文件,找到第一条记录'b a',所以肯定有镜像。但是文件是反转后的原文件,所以b a的镜像必须在同一个行号上。
要删除 a[a b],您可以使用 b[1].

找到索引

这些适用于作为字符的行,这意味着适用于各种数量的字段和各种长度的字段,而不仅仅是提供的示例。

第一个解决方案

awk -F "" '{r=""; for (i=NF;i;i--) r=r $i} !seen[[=10=]]++ && !seen[r]++' file

以上命令排除对称线,如:a a。我们可以像这样包含它们(打印一次):

awk -F "" '{r=""; for (i=NF;i;i--) r=r $i} !seen[[=11=]]++ && (!seen[r]++ || [=11=]==r)' file

或进一步改进,采用 Ed Morton 的想法,使用一个散列并始终保留两个字符串中较大的一个(这是按字母顺序比较)。

awk -F "" '{r=""; for (i=NF;i;i--) r=r $i} !seen[[=12=]>r? [=12=]: r]++' file

第二个解决方案

rev file | paste -d \n file - | awk '!seen[[=13=]]++ && NR%2'

此处使用换行符粘贴文件及其镜像,结果镜像为偶数行,仅对奇数行打印唯一值(但已测试所有行),awk 条件的顺序很重要,因为我们想要++ 也适用于偶数行。

常见的、惯用的 awk 方法是始终按特定顺序排列字段,不管它们的输入顺序如何,并将结果用作唯一性测试数组键,例如只有 2 个字段,如您的示例所示:

$ awk '!seen[> ?  FS  :  FS ]++' file
a b
c d

这结合了 Ed Morton 和 Homer 的答案,使其适用于任意数量的列,而 运行 它全部在 中(实际上是 gawk)。只会打印第一次出现,它也使用 GNU asort 扩展名:

awk 'function mkkey(line){
    res="";
    split(line, a);
    asort(a);
    for(i=1; i<=length(a); ++i) res = res FS a[i];
    return res
} !seen[mkkey([=10=])]++' file