it-swarm-ru.tech

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

У меня есть два текстовых файла. Первый имеет содержание:

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

Для решения проблемы "пустой ячейки" нам просто нужно -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 как не жадную"

71
glenn jackman

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

paste file1 file2 | pr -t -e24

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

12
Bruce Ediger

Обновление: Здесь гораздо более простой скрипт (тот, что в конце вопроса) для табличного вывода. Просто передайте ему имя файла, как и в 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 "[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 # Этот инструмент является общим для всех представленных ответов # Он может обрабатывать несколько файлов; поэтому несколько столбцов ... Хорошо! # Разграничивает каждый столбец с помощью табуляции ... Хорошо. # Его вывод не сведен в таблицу.

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

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, чтобы объединить два столбца с помощью Tab 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'_), а в любой оболочке вы можете использовать "$(printf '\t')".)

_paste left.txt right.txt | column -s '␉' -t
_
5
Gilles 'SO- stop being evil'

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

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

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

4
bahamat

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

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.

4
techno

Опираясь на ответ Багамата : это можно сделать полностью в 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 for 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 в двумерный массив.
  • Он читает все файлы, сохраняя все содержимое. Затем он записывает все вывод из блока END.
0