使用选项卡以视觉方式对齐 TSV

Visually align TSV using tabs

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

示例:

文件 1:

AA      BB      CCC
AAAA    BBB     CCC
AA      BBBB    CC

文件 2:

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

视觉对齐是供人类使用的,请勿以该格式保存文件,而是在您需要查看文件时使用 column 为您格式化。

首先需要删除第一个文件中的多余标签并合并文件

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

这将具有由分隔符(制表符)对齐的列。现在,只要您想查看将为您对齐列的文件,您就可以使用 column -ts$'\t' file12

这假设您没有遗漏字段。

我问这个问题是希望有一个现有的工具或一个简单的 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)
    }
}