如何在公共元素处对齐列,但为不同元素提供自己的行?

如何在公共元素处对齐列,但为不同元素提供自己的行?

我用来paste将三个文本文件(不需要排序)合并到一个包含三列的文档中。

paste a.txt b.txt c.txt

我希望列共有的元素占据同一行,而不与不匹配的元素共享它(它们目前这样做)。同样的道理,独特的元素应该有自己的行。每列中的元素应保留其原始顺序。

这是一个简单的例子。

输入

1 1 1
2 2 2
3 4 4
5 5 5
1 1 2
3 3 3

所需输出

1 1 1
2 2 2
3    
  4 4
5 5 5
1 1
    2
3 3 3

这是一个更复杂的例子。

输入

000 000 000
002 002 001
006 006 006
008 008 007
009 009 009
011 012 010
013 013 013
015 015 014
016 016 016
018 019 017
020 020 020
021 021 022
024 024 024
026 025 025
028 026 026
118 028 027
119 118 118
032 119 117
036 032 032
037 033 033
039 034 034
040 037 037
042 039 038
043 040 040
045 042 041
046 043 043
048 045 044
    046 046
    049 047

所需输出

000 000 000
        001
002 002
006 006 006
        007
008 008 
009 009 009
        010
011        
    012 
013 013 013
        014
015 015 
016 016 016
        017
018     
    019 
020 020 020
021 021 
        022
024 024 024
    025 025
026 026 026
        027
028 028 
118 118 118
        117
119 119 
032 032 032
    033 033
    034 034
036     
037 037 037
        038
039 039 
040 040 040
        041
042 042 
043 043 043
        044
045 045 
046 046 046
        047
048     
    049

理想情况下,我想使用 Linux/Unix 内置的工具。我还希望输出保留为具有三列的单个文档,例如> whatever.csv.

我所能得到的最接近的是sdiff在原始文本文件上运行,但是尽管这正确地对齐了文件共同共享的元素,但它并没有按照我想要的方式处理差异。

答案1

BEGIN {
    # We assume the default input field separator (changeable with "-F")
    # Output will be tab delimited.
    OFS = "\t"
}
{
    # The number of output records that this input record results in.
    k=0

    # "seen" records which new record a field should be part of.
    # There may be NF new records for each input record if all
    # fields are unique.
    delete seen

    # "a" holds all data for the new output records.
    # It's basically a 2-dimensional NFxNF matrix
    # encodod in a 1-dimensional array.
    delete a

    # Iterate over the fields
    for (i=1; i<=NF; ++i) {
        if (!seen[$i]) {
            # This data has not been seen before (in this input record),
            # assign it to the next output line.

            seen[$i] = ++k
        }

        # Assign the input field to the right spot
        a[(seen[$i]-1)*NF + i] = $i
    }

    # Save NF as this is reset by emptying $0 later.
    nf = NF

    # Create and output new lines
    for (j = 1; j<=k; ++j) {
        $0 = ""

        # Create new output record
        for (i = 1; i<=nf; ++i)
            $i = a[(j-1)*nf + i]

        # Output record
        print
    }
}

对给定数据进行测试:

$ awk -f script.awk file
1       1       1
2       2       2
3
        4       4
5       5       5
1       1
                2
3       3       3

对其他数据进行测试:

$ cat file
a b c e
1 2 1 1
2 1 1 1
1 1 1 2
$ awk -f script.awk file
a
        b
                c
                        e
1               1       1
        2
2
        1       1       1
1       1       1
                        2

答案2

paste这是 shell 脚本中使用和 的“强力”解决方案read

#!/bin/sh

paste a.txt b.txt c.txt |
while read -r a b c; do
    if [ "$a" = "$b" ] && [ "$b" = "$c" ]; then
        printf '%s\t%s\t%s\n' "$a" "$b" "$c"
    elif [ "$a" = "$b" ]; then
        printf '%s\t%s\n\t\t%s\n' "$a" "$b" "$c"
    elif [ "$a" = "$c" ]; then
        printf '%s\t\t%s\n\t%s\n' "$a" "$c" "$b"
    elif [ "$b" = "$c" ]; then
        printf '%s\n\t%s\t%s\n' "$a" "$b" "$c"
    else
        printf '%s\n\t%s\n\t\t%s\n' "$a" "$b" "$c"
    fi
done

可能有一个更优雅的解决方案,但我无法立即想出一个好的解决方案。

如果你愿意的话,你可以使用awk它——我认为结果看起来非常相似。 (使用的一个优点awk是它可以paste同时完成这项工作,如果这对您有用的话。)

相关内容