объединять текстовые файлы по столбцам

<p>у меня есть два текстовых файла. первый имеет содержание:</p><pre><code>languages recursively enumerable regular </code></pre><p>в то время как второй имеет содержание:</p><pre><code>minimal automaton turing machine finite </code></pre><p>я хочу объединить их в один файл по столбцам. поэтому я попытался <code>paste 1 2</code> и его вывод:</p><pre><code>languages minimal automaton recursively enumerable turing machine regular finite </code></pre><p>однако я хотел бы, чтобы столбцы были хорошо выровнены, например:</p><pre><code>languages minimal automaton recursively enumerable turing machine regular finite </code></pre><p>мне было интересно, если бы можно было достичь этого без ручной обработки?</p><hr><p>добавлено:</p><p>вот еще один пример, где метод брюса почти прибивает его, за исключением небольшого смещения, о котором я удивляюсь, почему?</p><pre><code>$ 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) 

вам просто нужна команда <a href="http://linux.die.net/man/1/column">column и указать ей использовать вкладки для разделения столбцовpaste file1 file2 | column -s $'\t' -t </code></pre><hr><p>для решения проблемы "пустой ячейки" нам просто нужно <code>-n</code> опция <code>column</code>:</p><pre><code>$ 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 </code></pre><p>моя страница справочника колонки показывает <code>-n</code> - это расширение debian gnu/linux. в моей системе fedora нет проблемы с пустыми ячейками: похоже, она получена из bsd, а на странице руководства написано, что "версия 2.23 изменила опцию -s как не жадную"</p>

<p>вы ищете удобную команду <code>pr</code>:</p><pre><code>paste file1 file2 | pr -t -e24 </code></pre><p>"-e24" - это "расширение табуляции до 24 пробелов". к счастью, <code>paste</code> помещает символ табуляции между столбцами, поэтому <code>pr</code> может его развернуть. я выбрал 24, посчитав символы в "recursively enumerable" и добавив 2.</p>

<p><em>обновление</em>: здесь гораздо более простой скрипт (тот, что в конце вопроса) для табличного вывода. просто передайте ему имя файла, как и в <code>paste</code>... он использует <code>html</code> для создания фрейма, чтобы его можно было настраивать. он сохраняет несколько пробелов, и выравнивание столбцов сохраняется, когда встречаются символы юникода. однако то, как редактор или зритель отображает юникод, это совсем другое дело ...</p><pre><code>┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐ │ 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 │ └──────────────────────┴────────────────┴──────────┴────────────────────────────┘ </code></pre><hr><pre><code>#!/bin/bash { echo -e "<html>\n<table border=1 cellpadding=0 cellspacing=0>" paste "$@" |sed -re 's#(.)#x091x09#' -e 's#x09# n #g' -e 's#^ ##' -e 's#n $#\n</tr>#' echo -e "</table>\n</html>" } |w3m -dump -t 'text/html' </code></pre><h2>---</h2><p><em>краткий обзор инструментов</em> представлен в ответах (пока).<br> я довольно внимательно посмотрел на них; вот что я нашел:</p><p><code>paste</code> # этот инструмент является общим для всех представленных ответов # он может обрабатывать несколько файлов; поэтому несколько столбцов ... хорошо! # разграничивает каждый столбец с помощью табуляции ... хорошо. # его вывод не сведен в таблицу.</p><p>все инструменты ниже всех удаляют этот разделитель! ... плохо, если вам нужен разделитель.</p><p><code>column</code> # он удаляет разделитель табуляции, поэтому поле идентифицируется исключительно по столбцам, которые, кажется, обрабатываются достаточно хорошо ... я не заметил ничего неправильного ... # помимо отсутствия уникального разделителя, он работает отлично!</p><p><code>expand</code> # имеет только одну настройку табуляции, поэтому она непредсказуема за пределами 2 столбцов # выравнивание столбцов не является точным при обработке юникода, и оно удаляет разделитель табуляции, поэтому идентификация полей выполняется только выравниванием столбцов</p><p><code>pr</code> # имеет только одну настройку вкладки, поэтому она непредсказуема за пределами 2 столбцов. # выравнивание столбцов не является точным при обработке юникода, и оно удаляет разделитель табуляции, поэтому поле идентифицируется исключительно по выравниванию столбца</p><p>для меня, <code>column</code> это очевидный лучший солютон в виде однострочного. если вы хотите использовать разделитель или ascii-арт табуляцию ваших файлов, читайте дальше, иначе .. <code>columns</code> чертовски хорошо :).</p><hr><p>вот скрипт, который принимает любой номер файла и создает табличную презентацию в стиле ascii. (имейте в виду, что юникод может не отображаться до ожидаемой ширины, например, ௵, который представляет собой один символ. это сильно отличается от столбца неверные числа, как в некоторых из утилит, упомянутых выше.) ... вывод скрипта, показанный ниже, получен из 4 входных файлов с именем f1 f2 f3 f4 ...</p><pre><code>+------------------------+-------------------+-------------------+--------------+ | languages | minimal automaton | chomsky hierarchy | grammars | | recursively enumerable | turing machine | type-0 | unrestricted | | regular | finite | — | | | alphabet | | symbol | | | | | | context | +------------------------+-------------------+-------------------+--------------+ </code></pre><hr><pre><code>#!/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 </code></pre><hr><p>вот мой оригинальный ответ (немного урезанный вместо вышеприведенного сценария)</p><p>используя <code>wc</code>, чтобы получить ширину столбца, и <code>sed</code> на правую панель с символом <em>видимым</em> <code>.</code> (только для этого примера) ... а затем <code>paste</code>, чтобы объединить два столбца с помощью <em>tab</em> char ...</p><pre><code>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........... 

ты почти там. 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}') </code>\_</pre><p>если у вас есть утилита для столбцов bsd, вы можете использовать ее, чтобы определить ширину столбца и развернуть вкладки за один раз. (\_<code>␉</code>\_ - буквенный символ табуляции; в bash/ksh/zsh вы можете использовать вместо него \_<code>$'t'_), а в любой оболочке вы можете использовать "$(printf '\t')"</code>.)</p><pre>\_<code>paste left.txt right.txt | column -s '␉' -t </code>\_</pre>

<p>это многошаговое, так что это не оптимально, но здесь идет.</p><p>1) найдите длину самой длинной строки в <code>file1.txt</code>.</p><pre><code>while read line do echo ${#line} done < file1.txt | sort -n | tail -1 в вашем примере самая длинная строка - 22.2) используйте awk для заполнения file1.txt, добавляя каждую строку длиной менее 22 символов до 22 с помощью оператора printf.awk 'fs="---" {printf "%-22sn", $1}' < file1.txt > file1-pad.txt </code></pre><p>примечание: для fs используйте строку, которая не существует в <code>file1.txt</code>.</p><p>3) используйте пасту, как вы делали раньше.</p><pre><code>$ paste file1-pad.txt file2.txt languages minimal automaton recursively enumerable turing machine regular finite если это то, что вы делаете часто, это легко можно превратить в сценарий.

я не могу комментировать ответ гленна джекмана, поэтому добавляю его, чтобы решить проблему пустых ячеек, которую заметил питер. добавление нулевого символа перед каждой вкладкой исключает использование разделителей, которые рассматриваются как один разрыв, и устраняет проблему. (первоначально я использовал пробелы, но использование нулевого символа исключает лишний пробел между столбцами.)paste file1 file2 | sed 's/t/0t/g' | column -s $'\t' -t </code></pre><p>если нулевой символ вызывает проблемы по разным причинам, попробуйте либо:</p><pre><code>paste file1 file2 | sed 's/\t/ \t/g' | column -s $'t' -t илиpaste file1 file2 | sed $'s/\t/ \t/g' | column -s $'t' -t и sed, и column, по-видимому, различаются по реализации в разных версиях и версиях unix/linux, особенно bsd (и mac os x) по сравнению с gnu/linux.

опираясь на <a href="https://unix.stackexchange.com/q/16443/80216#16445">ответ багамата : это можно сделать полностью в 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 </code></pre><p>как и во многих сценариях <code>awk</code> этого рода, вышеприведенное сначала читается как <code>file1</code>, сохраняя все данные в массиве <code>save</code> и одновременно вычисляя максимальную длину строки. тогда это читает <code>file2</code> и печатает сохраненное (<code>file1</code>) данные бок о бок с текущими (<code>file2</code>) данные. наконец, если <code>file1</code> длиннее <code>file2</code> (имеет больше строк), мы печатаем последние несколько строк <code>file1</code> (те, для которых нет соответствующей строки во втором столбце).</p><p>относительно формата <code>printf</code>:</p><ul> <li><code>"%-<i>nn</i>s"</code> печатает строку с выравниванием по левому краю в поле <em><code>nn</code></em> шириной.</li> <li><code>"%-*s", <i>nn</i></code> делает то же самое - <code>*</code> говорит, что нужно взять ширину поля из следующего параметра.</li> <li>используя <code>maxlength<b>+2</b></code> for <em><code>nn</code></em>, мы получаем два пробела между столбцами. очевидно, что <strong> <code>+2</code> </strong> можно настроить.</li> </ul><p>приведенный выше скрипт работает только для двух файлов. его можно легко изменить для обработки трех файлов или для обработки четырех файлов и т.д., но это было бы утомительно и оставлено в качестве упражнения. однако оказывается, что нетрудно изменить его для обработки <strong> <em> любое число</em> </strong> <em> of</em> файлы:</p><pre><code>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* </code></pre><p>это очень похоже на мой первый сценарий, кроме</p><ul> <li>получается <code>max\_length</code> в массив.</li> <li>получается <code>max\_fnr</code> в массив.</li> <li>он превращает <code>save</code> в двумерный массив.</li> <li>он читает <em> все</em> файлы, сохраняя <em> все</em> содержимое. затем он записывает <em> все</em> вывод из блока <code>end</code>.</li> </ul>