csv 使用翼片目视对齐TSV

gywdnpxw  于 2023-04-27  发布在  其他
关注(0)|答案(3)|浏览(118)

我有一个包含字段的文本文件,字段之间由若干后续制表符分隔(这样的领域都是视觉对齐)。我想添加了很多新的领域,它从另一个(不对齐,纯tsv)文件,同时保持所有内容对齐。很多值中包含空格,所以只有制表符(假定宽度为8)可用于对准,因为我希望以后能够通过在任意数量的后续制表符上拆分每一行来解析文件。这意味着我不能我不能使用像columntsv-pretty这样的工具,因为它们使用空格来对齐。有没有一个工具或简短的脚本可以用来实现我想要的?
示例:
文件1:

AA      BB      CCC
AAAA    BBB     CCC
AA      BBBB    CC

文件二:

DD  EE  FF
DDDD    EE  FFFF
DD  EEEE    FF

结果:

AA      BB      CCC
AAAA    BBB     CCC
AA      BBBB    CC
DD      EE      FF
DDDD    EE      FFFF
DD      EEEE    FF
2ledvvac

2ledvvac1#

视觉对齐是供人使用的,不要以这种格式保存文件,而是当你需要查看文件时,使用column为你格式化。
首先需要摆脱额外的标签在您的第一个文件和合并的文件

$ cat <(tr -s '\t' <file1) file2 > file12

它将通过分隔符(制表符)对齐列。现在你可以在任何时候使用column -ts$'\t' file12来查看文件,它将为你对齐列。
这假定您没有丢失的字段。

xtfmy6hx

xtfmy6hx2#

我问这个问题是希望有一个现有的工具或一个简单的awk/perl一行程序可以做我想要的。看起来没有,所以我用Go写了一个简单的工具来处理我的输入。它没有处理很多好的tsv解析器应该做的事情(比如转义),但也许它对其他人来说仍然有用:

package main

import (
    "bufio"
    "fmt"
    "math"
    "os"
    "strings"
)

const tabWidth = 8

func tsvAlign(filenames []string) (err error) {
    var lines [][]string
    for _, filename := range filenames {
        file, err := os.Open(filename)
        if err != nil {
            return err
        }
        defer file.Close()

        scanner := bufio.NewScanner(file)
        for scanner.Scan() {
            lines = append(lines, strings.FieldsFunc(scanner.Text(), func(c rune) bool { return c == '\t' }))
        }
    }

    maxFieldWidths := make([]int, len(lines[0])-1, len(lines[0])-1)
    for i := 0; i < len(lines[0])-1; i++ {
        for _, line := range lines {
            if len(line[i]) > maxFieldWidths[i] {
                maxFieldWidths[i] = len(line[i])
            }
        }
    }

    for _, line := range lines {
        for i, field := range line[:len(line)-1] {
            padding := int(math.Ceil(float64(maxFieldWidths[i]+tabWidth-maxFieldWidths[i]%tabWidth)/8 - float64(len(field))/8))
            fmt.Print(field, strings.Repeat("\t", padding))
        }
        fmt.Println(line[len(line)-1])
    }

    return err
}

func main() {
    if len(os.Args) < 2 {
        fmt.Fprintln(os.Stderr, "ERROR: No arguments provided")
        return
    }
    err := tsvAlign(os.Args[1:])
    if err != nil {
        fmt.Fprintln(os.Stderr, "ERROR: ", err)
    }
}
jdzmm42g

jdzmm42g3#

完全同意karakfa's answer的观点,如果你在数据文件中使用 single 制表符作为分隔符,让其他一些专门的工具(如column -t *)在事后为你做视觉对齐,你会让事情变得更容易。这是Makefile或Git预提交钩子的一个 * 伟大 * 用例。
既然你已经把这个问题标记为awk,我仍然觉得它应该得到一个基于AWK的答案,以满足你最初的要求。但是,与karakfa的一行程序相比,所有需要适应不寻常的输入文件格式的代码都应该告诉你一些事情。;)
作为将来的参考,要知道AWK中的-FFS实际上是一个正则表达式,因此您的分隔符不必是单个字符。直到我真正开始仔细阅读手册,我才注意到这一点。它的默认值类似于[ \t]+-即一个或多个连续的空格字符。
假设原始输入看起来像这样

echo -e "AA\t\tBBBBBBBB\tCCC
AAAAAAAA\tBBB\t\tCCC
AA\t\tBBBB\t\tCC
AA AA AA AA AA\tBBBB BBBB\tCC" > file1

echo -e "DD\tEE\tFF
DDDD\tEE\tFFFF
DD\tEEEE\tFF" > file2

。。。这 * 几乎 * 工作

cat > format.awk <<EOF
BEGIN {
    TABWIDTH = TABWIDTH ? TABWIDTH : 8
    FS = "\t+"
    OFS = "\t"
}
function ceil(x) {
    return (x == int(x)) ? x : int(x) + 1
}
{
    for (i=1; i<=NF; i++) {
        # store off values for later
        data[NR,i] = \$i
        if (length(\$i) > maxw[i])
            maxw[i] = length(\$i)
    }
}
END {
    for (i=1; i<=NR; i++) {
        for (j=1; j<NF; j++) {
            printf data[i,j]
            # at least 1, plus 1 for every multiple of TABWIDTH less than max
            needtabs = ceil((maxw[j] - length(data[i,j])) / TABWIDTH) + 1
            for (k=1; k<=needtabs; k++)
                printf OFS
        }
        printf "%s\n", data[i,NF]
    }
}
EOF

awk -v TABWIDTH=8 -f format.awk file1 file2

还在研究一些问题,我会相应地更新答案。
请注意,上面所有的$都被转义了,所以示例将在复制粘贴时运行。如果您只是将AWK代码复制粘贴到自己的脚本中,请手动修复这些代码。

  • 关于column的脚注:它看起来确实有一个line length limitation,如果你正在处理巨大的数据文件,你应该注意到这一点。我有一种感觉,这里不是这种情况。如果是这种情况,请查看mlr --tsv --opprint1xsv table -d2作为人类可读表示的替代方案。

相关问题