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

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, а не в вставить.
Брюс Эдигер
1
Я получаю то же самое с EM-DASH с обоими prи expand... columnsизбегаю этой проблемы.
Peter.O
Я создал вывод для большинства различных ответов, за исключением awk + paste , который сместит влево самый правый столбец (столбцы), если левый файл будет короче любого из правых. То же самое, и многое другое, относится к «paste + column», в котором также есть проблема с пустыми строками в левом столбце (столбцах) ... Если вы хотите увидеть все выходные данные вместе. вот ссылка: paste.ubuntu.com/643692 Я использовал 4 столбца.
Peter.O
Я просто заметил что - то вводит в заблуждение по paste.ubuntu ссылке ... Я изначально установить данные для тестирования своих сценариев, (и это привело к делать другим) ... так что поля , которые говорят , что , ➀ unicode may render oddly but the column count is ok безусловно , делает не применяется к wc-paste-prи wc-paste-prони показать разницу в количестве столбцов .. Остальные в порядке.
Peter.O
1
@BruceEdiger: проблема выравнивания возникает, когда используются символы не ASCII (в его вопросе OP использовал тире (-) вместо символа минус (-)), скорее всего, из-за плохой обработки или отсутствия обработки prмногобайтовыми символы в текущей локали (обычно UTF8).
WhiteWinterWolf

Ответы:

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

На странице man моей колонки указано -n«Расширение Debian GNU / Linux». В моей системе Fedora нет проблемы с пустыми ячейками: похоже, она получена из BSD, а на странице руководства написано, что «Версия 2.23 изменила опцию -s как не жадную»

Гленн Джекман
источник
4
Гленн: Ты герой часа! Я знал, что там что-то подобное, но я не мог вспомнить это. Я скрывался в этом вопросе; жду тебя :) ... columnконечно; насколько очевидно (задним числом) +1 ... Спасибо ...
Peter.O
4
Я только что заметил, что column -s $'\t' -tигнорирует пустые ячейки , в результате чего все последующие ячейки справа от него (в этой строке) перемещаются влево; то есть в результате пустой строки в файле, или она была короче ... :(
Peter.O
1
@masi, исправлено
Гленн Джекман
-n не работает в RHEL. Есть ли альтернатива?
Кошур
Наконец, я могу прокомментировать, поэтому хочу заметить, что ранее я добавил ответ ниже, который решает проблему Peter.O с прогонами пустых ячеек с использованием нулей.
техно
11

Вы ищете удобную prкоманду денди :

paste file1 file2 | pr -t -e24

«-E24» означает «развернуть табуляцию до 24 пробелов». К счастью, pasteставит символ табуляции между столбцами, чтобы prможно было его развернуть. Я выбрал 24, посчитав символы в «Recursively enumerable» и добавив 2.

Брюс Эдигер
источник
Спасибо! Что означает «расширение табуляции до 24 пробелов»?
Тим
Я также обновляю пример, где ваш метод почти прибивает его, за исключением небольшого смещения.
Тим
Традиционно «таб-стопы» попадают в каждые 8 ​​пробелов. «123TABabc» будет напечатан с символом «a» шириной 8 символов от начала строки. Если задать 24, то «а» будет иметь ширину 24 символа от начала строки.
Брюс Эдигер
Вы говорите , что «-e24» является «расширение табуляции 24 пространств» , так почему бы не использовать expandкоманду непосредственно: paste file1 file2 | expand -t 24?
WhiteWinterWolf
1
@Masi - мой ответ похож, но менее сложен, чем ответ @ techno ниже. Это не вызывает, sedпоэтому есть один процесс, который не запускается. prЯ думаю, он использует древнюю команду, относящуюся к дням Unix SysV, поэтому он может существовать при большем количестве установок, чем expand. Короче, это просто старая школа.
Брюс Эдигер
9

Обновление : здесь гораздо более простой скрипт (тот, что в конце вопроса) для табличного вывода. Просто передайте ему имя файла, как вы бы paste... Он использует htmlдля создания фрейма, так что он настраивается. Он сохраняет несколько пробелов, и выравнивание столбцов сохраняется, когда встречаются символы Юникода. Однако то, как редактор или зритель отображает юникод, это совсем другое дело ...

┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐
│ 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# Этот инструмент является общим для всех представленных ответов # Он может обрабатывать несколько файлов; поэтому несколько столбцов ... Хорошо! # Разграничивает каждый столбец с помощью табуляции ... Хорошо. # Его вывод не сведен в таблицу.

Все инструменты ниже всех удаляют этот разделитель! ... Плохо, если вам нужен разделитель.

column # Он удаляет разделитель табуляции, поэтому поле идентифицируется исключительно по столбцам, которые, кажется, обрабатываются довольно хорошо ... Я не заметил ничего неправильного ... # Помимо отсутствия уникального разделителя, он работает отлично!

expand # Имеет только одну настройку вкладки, поэтому она непредсказуема за пределами 2 столбцов # Выравнивание столбцов не является точным при обработке юникода, и оно удаляет разделитель табуляции, поэтому идентификация полей производится исключительно выравниванием столбцов

pr# Имеет только одну настройку вкладки, поэтому она непредсказуема за пределами 2 столбцов. # Выравнивание столбцов не является точным при обработке юникода, и оно удаляет разделитель табуляции, поэтому поле идентифицируется исключительно по выравниванию столбца

Для меня columnэто очевидный лучший солютон в качестве однострочного. Если вы хотите использовать разделитель или ASCII-арт табуляцию ваших файлов, читайте дальше, иначе ... columnsчертовски хорошо :) ) ...


Вот скрипт, который принимает любой номер файла и создает табличную презентацию в стиле ASCII. (Имейте в виду, что юникод может не отображаться до ожидаемой ширины, например, ௵, который представляет собой один символ. Это сильно отличается от столбца неверные числа, как в некоторых из утилит, упомянутых выше.) ... Вывод скрипта, показанный ниже, получен из 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...........
Peter.O
источник
Спасибо! Вы проделали довольно большую работу. Это восхитительно.
Тим
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, вы можете использовать ее, чтобы определить ширину столбцов и развернуть вкладки за один раз. ( является буквальным символом табуляции; $'\t'вместо bash / ksh / zsh вы можете использовать его и в любой оболочке, которую можете использовать "$(printf '\t')".)

paste left.txt right.txt | column -s '␉' -t
Жиль "ТАК - перестань быть злым"
источник
В моей версии wcкоманда должна быть: wc -L <left.txt... потому что, когда имя файла ускоряется как аргумент командной строки arg , его имя выводится на стандартный вывод
Peter.O
4

Это многошаговое, так что это не оптимально, но здесь идет.

1) Найдите длину самой длинной строки в file1.txt.

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

В вашем примере самая длинная строка - 22.

2) Используйте awk для file1.txtзаполнения, добавляя в каждую строку менее 22 символов до 22 с printfоператором.

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

Если это то, что вы делаете часто, это легко можно превратить в сценарий.

bahamat
источник
В вашем коде нужно найти самую длинную строку while IFS= read -r line, в противном случае оболочка будет искажать пробелы и обратные косые черты. Но оболочка не лучший инструмент для этой работы; Последние версии GNU Coreutils есть wc -L(см ответ Фреда), или вы можете использовать AWK: awk 'n<length {n=length} END {print +n}'.
Жиль "ТАК - перестань быть злым"
4

Я не могу комментировать ответ Гленна Джекмана, поэтому добавляю его, чтобы решить проблему пустых ячеек, которую заметил Питер. Добавление нулевого символа перед каждой вкладкой исключает использование разделителей, которые рассматриваются как один разрыв, и устраняет проблему. (Первоначально я использовал пробелы, но использование нулевого символа исключает лишний пробел между столбцами.)

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

И то, sedи другое, по- columnвидимому, различаются по реализации в разных версиях и версиях Unix / Linux, особенно BSD (и Mac OS X) по сравнению с GNU / Linux.

техно
источник
Эта команда sed ничего не делает. Я заменяю команду столбца на od -cи не вижу нулевых байтов. Это на Centos и Ubuntu.
Гленн Джекман
1
Это сработало для меня в RedHat EL4. И sed, и column, похоже, меняются со временем и системой. В Ubuntu 14.4 использование \0не работало как nullsed, но работало \x0. Однако тогда столбец выдал line too longошибку. Кажется, проще всего использовать пробел и жить с дополнительным персонажем.
техно
0

Опираясь на ответ Багамата : это можно сделать целиком 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блока.
G-Man говорит: «Восстанови Монику»
источник
Я знаю, что этот вопрос старый; Я просто наткнулся на это. Я согласен, что pasteэто лучшее решение; в частности, Гленн Джекман paste file1 file2 | column -s $'\t' -t. Но я подумал, что было бы интересно попытаться улучшить awkподход.
G-Man говорит: «Восстанови Монику»