逐列合并文本文件


52

我有两个文本文件。第一个内容:

Languages
Recursively enumerable
Regular

而第二个内容:

Minimal automaton
Turing machine
Finite

我想将它们按列合并到一个文件中。所以我尝试了paste 1 2,它的输出是:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

但是我想使列很好地对齐,例如

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

我想知道如果不手动处理是否有可能实现?


添加:

这是另一个例子,布鲁斯方法几乎钉住了它,除了一些不对齐的地方,我不知道为什么?

$ cat 1
Chomsky hierarchy
Type-0
—

$ cat 2
Grammars
Unrestricted

$ paste 1 2 | pr -t -e20
Chomsky hierarchy   Grammars
Type-0              Unrestricted
—                    (no common name)

3
最后一个未对齐的示例令人费解。我可以在Arch linux,pr(GNU coreutils)8.12上复制它。我不能在老的Slackware(11.0)上复制它,我也有:pr(GNU coreutils)5.97。问题出在'-'字符,它在pr中,而不是粘贴。
Bruce Ediger

1
EM-DASH兼有prexpand... columns可以避免相同的问题。
Peter.O 2011年

awk + ​​paste以外,我为大多数不同的答案生成了输出,如果左文件短于右,则将最右列左移。同样的内容,也适用于“粘贴+列”,如果左行中有空白行,也会出现此问题...如果您想一起查看所有输出。这里是链接:paste.ubuntu.com/643692我已经使用了4列。
Peter.O 2011年

我只是注意到的东西误导paste.ubuntu链接...我本来订了我的测试脚本,数据连接(且导致上做其他人)...所以这不能不领域➀ unicode may render oddly but the column count is ok 绝对不会适用于wc-paste-prwc-paste-pr他们确实显示列数差异。其他都可以。
Peter.O 2011年

1
@BruceEdiger:使用非ASCII字符时出现对齐问题(在他的问题中,OP使用破折号(-)而不是减号(-)),这很可能是由于对pr多字节的处理不好或没有进行处理当前语言环境中的字符(通常为UTF8)。
WhiteWinterWolf

Answers:


68

您只需要column命令,并告诉它使用制表符分隔列

paste file1 file2 | column -s $'\t' -t

为了解决“空单元”的争议,我们只需要-n选择column

$ paste <(echo foo; echo; echo barbarbar) <(seq 3) | column -s $'\t' -t
foo        1
2
barbarbar  3

$ paste <(echo foo; echo; echo barbarbar) <(seq 3) | column -s $'\t' -tn
foo        1
           2
barbarbar  3

我的专栏手册页指出-n它是“ Debian GNU / Linux扩展”。我的Fedora系统没有出现空单元问题:它似乎是从BSD派生的,并且手册页上显示“版本2.23将-s选项更改为非贪婪”


4
格伦:你是当下的英雄!我知道周围有这样的事情,但我无法记住。我一直在潜伏这个问题。等着 :) ... column,当然;(事后看来)+1有多明显...谢谢...
Peter.O 2011年

4
我刚刚注意到,它column -s $'\t' -t忽略了空单元格,导致其右边(在该行上)的所有后续单元格都向左移动;即,由于文件中的空白行,或者它更短... :(
Peter.O 2011年

1
@masi,更正
格伦·杰克曼

-n在RHEL中不起作用。还有其他选择吗?
科苏尔

我最后可以发表评论,所以要注意,我之前在下面添加了一个答案,该答案通过使用null来解决Peter.O的空单元格运行问题。
techno

11

您正在寻找方便的dandy pr命令:

paste file1 file2 | pr -t -e24

“ -e24”是“将制表符扩展到24个空格”。幸运的是,paste在各列之间放置了制表符,因此pr可以对其进行扩展。通过计算“递归可枚举”中的字符并添加2,我选择了24。


谢谢!“将制表符扩展到24个空格”是什么意思?
蒂姆(Tim)

我还举了一个示例,其中您的方法除了轻微的未对准外几乎钉住了它。
蒂姆

传统上,“制表符”每8个空格命中一次。从行首开始,将以8个字符的宽度打印出“ 123TABabc”字符。将其设置为24会将'a'设置为从行首开始的24个字符的宽度。
Bruce Ediger

你说的“-e24”是“扩大制表位到24位”,那么为什么不使用expand直接命令:paste file1 file2 | expand -t 24
WhiteWinterWolf

1
@Masi-我的答案与以下@techno的答案相似,但不那么复杂。它不会调用,sed因此有一个进程没有运行。pr我认为它使用的是一个古老的命令,可以追溯到Unix SysV时代,因此它可能存在于比SysV更多的安装中expand。简而言之,这只是一所老学校。
Bruce Ediger

9

更新:这是一个用于列表输出的更简单的脚本(该问题末尾的脚本)。只需将文件名传递给它即可,因为pastehtml用来制作框架,因此可以进行调整。它确实保留了多个空格,并且遇到Unicode字符时,将保留列对齐。但是,编辑器或查看器呈现unicode的方式完全是另一回事...

┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐
│ Languages            │ Minimal        │ Chomsky  │ Unrestricted               │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ Recursive            │ Turing machine │ Finite   │     space indented         │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ Regular              │ Grammars       │          │ ➀ unicode may render oddly │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ 1 2  3   4    spaces │                │ Symbol-& │ but the column count is ok │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│                      │                │          │ Context                    │
└──────────────────────┴────────────────┴──────────┴────────────────────────────┘

#!/bin/bash
{ echo -e "<html>\n<table border=1 cellpadding=0 cellspacing=0>"
  paste "$@" |sed -re 's#(.*)#\x09\1\x09#' -e 's#\x09# </pre></td>\n<td><pre> #g' -e 's#^ </pre></td>#<tr>#' -e 's#\n<td><pre> $#\n</tr>#'
  echo -e "</table>\n</html>"
} |w3m -dump -T 'text/html'

---

答案中提供的工具的提要(到目前为止)。
我仔细看了看它们;这是我发现的:

paste#到目前为止,所有答案都使用此工具。#它可以处理多个文件;因此多列...好!#用Tab分隔每列...好。#其输出未制成表格。

下面的所有工具都删除了该定界符!...如果需要定界符,则不好。

column #它删除了制表符分隔符,因此字段标识纯粹是按列处理的,它似乎处理得很好。.我没发现任何问题...#除了没有唯一的分隔符,它还可以正常工作!

expand #仅具有单个制表符设置,因此超出2列是无法预测的##处理unicode时列的对齐方式不准确,并且删除了制表符定界符,因此字段标识完全是通过列对齐

pr#仅具有一个选项卡设置,因此超过2列将无法预测。#在处理unicode时,列的对齐方式不准确,并且删除了制表符分隔符,因此字段标识纯粹是通过列对齐

对我来说,column它是单线显然最好的解决方案。如果您想使用文件的分隔符或ASCII格式的表,请继续阅读,否则.. columns太好了:)...


这是一个脚本,它使用任意数量的文件并创建ASCII格式的列表表示。.(请注意,Unicode可能无法呈现为预期的宽度,例如௵,它是单个字符。这与该列完全不同数字是错误的,例如上面提到的某些实用程序。)...脚本的输出,如下所示,来自4个输入文件,名为F1 F2 F3 F4 ...

+------------------------+-------------------+-------------------+--------------+
| Languages              | Minimal automaton | Chomsky hierarchy | Grammars     |
| Recursively enumerable | Turing machine    | Type-0            | Unrestricted |
| Regular                | Finite            | —                 |              |
| Alphabet               |                   | Symbol            |              |
|                        |                   |                   | Context      |
+------------------------+-------------------+-------------------+--------------+

#!/bin/bash

# Note: The next line is for testing purposes only!
set F1 F2 F3 F4 # Simulate commandline filename args $1 $2 etc...

p=' '                                # The pad character
# Get line and column stats
cc=${#@}; lmax=                      # Count of columns (== input files)
for c in $(seq 1 $cc) ;do            # Filenames from the commandline 
  F[$c]="${!c}"        
  wc=($(wc -l -L <${F[$c]}))         # File length and width of longest line 
  l[$c]=${wc[0]}                     # File length  (per file)
  L[$c]=${wc[1]}                     # Longest line (per file) 
  ((lmax<${l[$c]})) && lmax=${l[$c]} # Length of longest file
done
# Determine line-count deficits  of shorter files
for c in $(seq 1 $cc) ;do  
  ((${l[$c]}<lmax)) && D[$c]=$((lmax-${l[$c]})) || D[$c]=0 
done
# Build '\n' strings to cater for short-file deficits
for c in $(seq 1 $cc) ;do
  for n in $(seq 1 ${D[$c]}) ;do
    N[$c]=${N[$c]}$'\n'
  done
done
# Build the command to suit the number of input files
source=$(mktemp)
>"$source" echo 'paste \'
for c in $(seq 1 $cc) ;do
    ((${L[$c]}==0)) && e="x" || e=":a -e \"s/^.{0,$((${L[$c]}-1))}$/&$p/;ta\""
    >>"$source" echo '<(sed -re '"$e"' <(cat "${F['$c']}"; echo -n "${N['$c']}")) \'
done
# include the ASCII-art Table framework
>>"$source" echo ' | sed  -e "s/.*/| & |/" -e "s/\t/ | /g" \'   # Add vertical frame lines
>>"$source" echo ' | sed -re "1 {h;s/[^|]/-/g;s/\|/+/g;p;g}" \' # Add top and botom frame lines 
>>"$source" echo '        -e "$ {p;s/[^|]/-/g;s/\|/+/g}"'
>>"$source" echo  
# Run the code
source "$source"
rm     "$source"
exit

这是我的原始答案(代替上面的脚本整理了一下)

使用wc得到的列宽,并sed与右侧垫可见的字符.(只是在这个例子中)...然后paste加入一个两列标签字符...

paste <(sed -re :a -e 's/^.{1,'"$(($(wc -L <F1)-1))"'}$/&./;ta' F1) F2

# output (No trailing whitespace)
Languages.............  Minimal automaton
Recursively enumerable  Turing machine
Regular...............  Finite

如果要填充右列:

paste <( sed -re :a -e 's/^.{1,'"$(($(wc -L <F1)-1))"'}$/&./;ta' F1 ) \
      <( sed -re :a -e 's/^.{1,'"$(($(wc -L <F2)-1))"'}$/&./;ta' F2 )  

# output (With trailing whitespace)
Languages.............  Minimal automaton
Recursively enumerable  Turing machine...
Regular...............  Finite...........

谢谢!您已经做了很多工作。太棒了。
蒂姆(Tim)

5

你快到了。paste在每列之间放置一个制表符,因此您只需展开选项卡即可。(我假设您的文件不包含选项卡。)您确实需要确定左列的宽度。使用(最近)的GNU实用程序,可以wc -L显示最长行的长度。在其他系统上,请先使用awk。这+1是您要在各列之间保留的空白量。

paste left.txt right.txt | expand -t $(($(wc -L <left.txt) + 1))
paste left.txt right.txt | expand -t $(awk 'n<length {n=length} END {print n+1}')

如果您具有BSD列实用程序,则可以使用它来确定列宽并一次性扩展选项卡。(是字面量的制表符;可以在bash / ksh / zsh下使用$'\t',而在任何shell中都可以使用"$(printf '\t')"。)

paste left.txt right.txt | column -s '␉' -t

在我的版本中wc,命令必须为:wc -L <left.txt...因为,当将文件名作为命令行arg进行
修饰时

4

这是多步操作,因此不是最佳选择,但请按此处。

1)在中找到最长的线的长度file1.txt

while read line
do
echo ${#line}
done < file1.txt | sort -n | tail -1

在您的示例中,最长的行是22。

2)使用awk file1.txt填充,用printf语句填充每行少于22个字符,最多填充22个字符。

awk 'FS="---" {printf "%-22s\n", $1}' < file1.txt > file1-pad.txt

注意:对于FS,请使用中不存在的字符串file1.txt

3)像以前一样使用粘贴。

$ paste file1-pad.txt file2.txt
Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

如果您经常这样做,则可以轻松地将其转换为脚本。


在您的代码中找到最长的行,您需要while IFS= read -r line,否则shell将破坏空格和反斜杠。但是shell并不是完成这项工作的最佳工具。最近的GNU的coreutils版本的wc -L(见弗雷德的答案),或者你可以用awk: awk 'n<length {n=length} END {print +n}'
吉尔(Gilles)“所以,别再邪恶了”,

4

我无法评论glenn jackman的答案,因此添加此内容是为了解决Peter.O指出的空单元格问题。在每个选项卡之前添加null字符可消除将定界符运行视为单个中断并解决该问题的情况。(我最初使用空格,但是使用null char消除了列之间的多余空间。)

paste file1 file2 | sed 's/\t/\0\t/g' | column -s $'\t' -t

如果空字符由于各种原因导致问题,请尝试以下任一方法:

paste file1 file2 | sed 's/\t/ \t/g' | column -s $'\t' -t

要么

paste file1 file2 | sed $'s/\t/ \t/g' | column -s $'\t' -t

双方sedcolumn展示在口味和Unix / Linux,BSD特别是(和Mac OS X)与GNU / Linux版本实现改变。


该sed命令似乎无能为力。我用替换了column命令,od -c但看不到任何空字节。这是在centos和ubuntu上。
格伦·杰克曼(Glenn Jackman)

1
这在RedHat EL4中为我工作。sed和column似乎都随时间和系统而变化。在Ubuntu 14.4中,使用\0不能作为nullsed使用,但可以\x0。但是,然后列给出了一个line too long错误。最简单的事情似乎是使用空间并与多余的角色一起生活。
techno

0

建立在bahamat的答案上:可以完全在中完成awk,只读取一次文件,而不创建任何临时文件。要解决上述问题,请执行

awk '
        NR==FNR { if (length > max_length) max_length = length
                  max_FNR = FNR
                  save[FNR] = $0
                  next
                }
                { printf "%-*s", max_length+2, save[FNR]
                  print
                }
        END     { if (FNR < max_FNR) {
                        for (i=FNR+1; i <= max_FNR; i++) print save[i]
                  }
                }
    '   file1 file2

与许多awk类似的脚本一样,以上内容首先读取file1,将所有数据保存在save数组中,同时计算最大行长。然后,它读取file2 并打印保存的(file1)数据和当前(file2)数据。最后,如果file1的长度大于file2(具有更多行),我们将打印出的最后几行file1 (第二列中没有对应的行)。

关于printf格式:

  • "%-nns"在字段nn字符宽范围内打印左对齐的字符串。
  • "%-*s", nn做同样的事情- *告诉它从下一个参数获取字段宽度。
  • 通过使用for ,我们在列之间获得了两个空格。显然可以调整。maxlength+2nn+2

上面的脚本仅适用于两个文件。可以对其进行微不足道的修改以处理三个文件,或处理四个文件等,但这将是乏味的,因此留作练习。但是,事实证明修改它以处理任意数量文件并不困难 :

awk '
        FNR==1  { file_num++ }
                { if (length > max_length[file_num]) max_length[file_num] = length
                  max_FNR[file_num] = FNR
                  save[file_num,FNR] = $0
                }
        END     { for (j=1; j<=file_num; j++) {
                        if (max_FNR[j] > global_max_FNR) global_max_FNR = max_FNR[j]
                  }
                  for (i=1; i<=global_max_FNR; i++) {
                        for (j=1; j<file_num; j++) printf "%-*s", max_length[j]+2, save[j,i]
                        print save[file_num,i]
                  }
                }
    '   file*

这与我的第一个脚本非常相似,除了

  • 它变成max_length一个数组。
  • 它变成max_FNR一个数组。
  • 它变成save一个二维数组。
  • 它读取所有文件,保存所有内容。然后它写出该块的所有输出END

我知道这个问题是古老的。我偶然发现了它。我同意这paste是最好的解决方案。特别是glenn jackman's paste file1 file2 | column -s $'\t' -t。但是我认为尝试改进该awk方法会很有趣。
G-人
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.