it-swarm.cn

逐列合并文本文件

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

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)
54
Tim

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

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

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

$ 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选项更改为非贪婪”

71
glenn jackman

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

paste file1 file2 | pr -t -e24

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

12
Bruce Ediger

更新:这是一个用于列表输出的更简单的脚本(该问题末尾的脚本)。只需将文件名传递给它,就像传递给paste...一样。它使用html制作框架,因此可以进行调整。它确实保留了多个空格,并且遇到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 "[email protected]" |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可见字符.(仅在此示例中)...然后_pasteTab char连接两列...

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...........
9
Peter.O

您快到了。 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
_
5

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

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

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

4
bahamat

我无法评论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。

4
techno

建立在 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做同样的事情-*告诉它从下一个参数获取字段宽度。
  • 通过将maxlength+2用于nn,我们在列之间获得了两个空格。显然+2可以调整。

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

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转换为二维数组。
  • 它读取all文件,保存all目录。然后它写出allEND块的输出。
0