Транспонирование строк и столбцов

18

У меня есть файл со строками, как показано ниже.

title1:A1
title2:A2
title3:A3
title4:A4
title5:A5

title1:B1
title2:B2
title3:B3
title4:B4
title5:B5

title1:C1
title2:C2
title3:C3
title4:C4
title5:C5

title1:D1
title2:D2
title3:D3
title4:D4
title5:D5

Как мне этого добиться?

title1    title2     title3    title4
A1         A2         A3         A4
B1         B2         B3         B4
C1         C2         C3         C4
D1         D2         D3         D4

Дэнс
источник
пожалуйста, пожалуйста, пожалуйста, не используйте awk, вы можете также свернуть собственное решение с perl или python или реальным языком программирования или использовать tr / cut с несколькими проходами, чтобы получить то, что вы хотите
Рудольф Олах

Ответы:

14

Посмотрите на GNU datamash, который можно использовать как datamash transpose. Будущая версия также будет поддерживать кросс-табуляцию (сводные таблицы)

Падрейг Брейди
источник
9

Помимо использования собственного решения для транспонирования строк со столбцами из командной строки, единственный инструмент, который я когда-либо видел, может это сделать - это инструмент, называемый по иронии судьбы transpose.

Установка

К сожалению, его нет в репозитории, поэтому вам нужно скачать и скомпилировать его. Это довольно просто, поскольку у него нет дополнительных библиотек, от которых он зависит. Это можно сделать так:

$ gcc transpose.c -o transpose

использование

Он может легко обрабатывать простые текстовые файлы. Например:

$ cat simple.txt 
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11

Можно транспонировать с помощью этой команды:

$ transpose -t --fsep " " simple.txt 
X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11

Эта команда предназначена transposeдля transpose ( -t), а используемый разделитель полей - это пробел ( --fsep " ").

Ваш пример

Поскольку ваши образцы данных представлены в несколько более сложном формате, их необходимо обрабатывать в 2 этапа. Сначала нам нужно перевести его в формат, с которым transposeможно иметь дело.

Запустив эту команду, вы поместите данные в более удобный формат:

$ sed 's/:/ /; /^$/d' sample.txt \
    | sort | paste - - - - -
title1 A1   title1 B1   title1 C1   title1 D1   title2 A2
title2 B2   title2 C2   title2 D2   title3 A3   title3 B3
title3 C3   title3 D3   title4 A4   title4 B4   title4 C4
title4 D4   title5 A5   title5 B5   title5 C5   title5 D5

Теперь нам просто нужно удалить вторичные вхождения title1, title2 и т.д .:

$ sed 's/:/ /; /^$/d' sample.txt \
    | sort | paste - - - - - | sed 's/\ttitle[0-9] / /g'
title1 A1 B1 C1 D1 A2
title2 B2 C2 D2 A3 B3
title3 C3 D3 A4 B4 C4
title4 D4 A5 B5 C5 D5

Это теперь в формате, который transposeможет иметь дело с. Следующая команда сделает всю транспозицию:

$ sed 's/:/ /; /^$/d' sample.txt \
    | sort | paste - - - - - | sed 's/\ttitle[0-9] / /g' \
    | transpose -t --fsep " "
title1 title2 title3 title4
A1 B2 C3 D4
B1 C2 D3 A5
C1 D2 A4 B5
D1 A3 B4 C5
A2 B3 C4 D5
SLM
источник
8

Вы можете использовать их awkдля обработки данных pasteи columnих форматирования.

Здесь я предполагаю, что title1это только пример в вашем посте, и эти данные не содержат ничего, :кроме как разделитель между заголовком + данные.

nуказывает, сколько столбцов нужно напечатать (должно совпадать с тире paste).

awk -F":" -v n=4 \
'BEGIN { x=1; c=0;} 
 ++c <= n && x == 1 {print $1; buf = buf $2 "\n";
     if(c == n) {x = 2; printf buf} next;}
 !/./{c=0;next}
 c <=n {printf "%s\n", $2}' datafile | \
 paste - - - - | \
 column -t -s "$(printf "\t")"

Если вы хотите сделать его более гибким и простым в обслуживании, вы можете написать его в виде скрипта. Вот пример использования оболочки bash для awkи переданного по каналу column. Таким образом, вы также можете выполнять дополнительную проверку данных, например, проверяя правильность заголовков во всех строках и т. Д.

Обычно используется как:

$ ./trans -f data -c 4
title one  title two  title three  title four
A1         A2         A3           A4
B1         B2         B3           B4
C1         C2         C3           C4
D1         D2         D3           D4

Если заголовки всегда короче , чем данные , которые вы можете также сохранить ширины заголовка, то printfс %-*sи пропустить columnвсе вместе.

#!/bin/bash

trans()
{
    awk -F":" -v ncol="$1" '
    BEGIN {
        level = 1 # Run-level.
        col   = 1 # Current column.
        short = 0 # If requested to many columns.
    }
    # Save headers and data for row one.
    level == 1 {
        head[col] = $1
        data[col] = $2
        if (++col > ncol) { # We have number of requested columns.
            level = 2
        } else if ($0 == "") { # If request for more columns then available.
            level = 2
            ncol  = col - 2
            short = 1
        } else {
            next
        }
    }
    # Print headers and row one.
    level == 2 {
        for (i = 1; i <= ncol; ++i)
            printf("%s\t", head[i])
        print ""
        for (i = 1; i <= ncol; ++i)
            printf("%s\t", data[i])
        level = 3
        col = ncol + 1
        if (!short)
            next
    }
    # Empty line, new row.
    ! /./ { print ""; col = 1; next }
    # Next cell.
    col > ncol {next}
    {
        printf "%s%s", $2, (col <= ncol) ? "\t" : ""
        ++col
    }
    END {print ""}
    ' "$2"
}

declare -i ncol=4  # Columns defaults to four.
file=""            # Data file (or pipe).

while [[ -n "$1" ]]; do
    case "$1" in
    "-c") ncol="$2"; shift;;
    "-f") file="$2"; shift;;
    *) printf "Usage: %s [-c <columns>] [-f <file> | pipe]\n" \
        "$(basename $0)" >&2;
        exit;;
    esac
    shift
done

trans "$ncol" "$file" | column -t -s "$(printf "\t")"
Runium
источник
1
Хороший ответ! @JoelDavis и я взломали это, но ваш ответ потрясающий!
SLM
7

Вот быстрый способ поместить файл в нужный вам формат:

$ grep -Ev "^$|title5" sample.txt | sed 's/title[0-9]://g' | paste - - - -
A1  A2  A3  A4
B1  B2  B3  B4
C1  C2  C3  C4
D1  D2  D3  D4

Если вы хотите заголовки столбцов:

$ grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t'; \
    echo ""; \
    grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -
title1  title2  title3  title4  
A1      A2      A3      A4
B1      B2      B3      B4
C1      C2      C3      C4
D1      D2      D3      D4

Как работает 2-я команда

печать баннера
grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t';
положить возврат после баннера в
echo
печать строк данных
grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -
SLM
источник
Команда вставки просто сделала мою работу. спасибо за ответ ...
СК Венкат
3

Вероятно, есть более краткий способ сформулировать это, но, похоже, это дает общий эффект:

[jadavis84@localhost ~]$ sed 's/^title[2-9]://g' file.txt | tr '\n' '\t' | sed 's/title1:/\n/g' ; echo

A1  A2  A3  A4  A5      
B1  B2  B3  B4  B5      
C1  C2  C3  C4  C5      
D1  D2  D3  D4  D5  
[jadavis84@localhost ~]$ 

множественный sed вызовов не кажутся правильными (и я уверен, что sed может также выполнить перевод новой строки), так что это, вероятно, не самый простой способ сделать это. Кроме того, это удаляет потенциальные заголовки, но вы можете сгенерировать их вручную, если вы правильно отформатировали строки / поля.

Лучший ответ, вероятно, будет сводить этот эффект к простому использованию sedили awkвыполнению этого, чтобы за один раз происходило только одно. Но я устал, так что это то, что я смог собрать.

Bratchley
источник
Джоэл - я сделал ту же ошибку и только что заметил, он не хочет столбец title5 в выводе.
SLM
Ах, хорошо пробежав через awk в конце концов, это надо исправить. Но похоже, что Sukminder опубликовал полное решение.
Братчли
1

pasteэто, вероятно, ваш лучший выбор. Вы можете извлечь соответствующие биты с cut, grepи awkэто нравится:

(awk 'NR==1' RS= infile | cut -d: -f1; cut -sd: -f2 infile)

Если пятый столбец должен быть удален, добавьте awk 'NR%5'так:

(awk 'NR==1' RS= infile | cut -d: -f1; cut -sd: -f2 infile) | awk 'NR%5'

Теперь колонку с paste:

(awk 'NR==1' RS= infile | cut -d: -f1; cut -sd: -f2 infile) | awk 'NR%5' | paste - - - -

Выход:

title1  title2  title3  title4
A1  A2  A3  A4
B1  B2  B3  B4
C1  C2  C3  C4
D1  D2  D3  D4
Тор
источник
0

Только для части транспонирования у меня недавно была похожая проблема, и я использовал:

awk -v fmt='\t%4s'  '{ for(i=1;i<=NF;i++){ a[i]=a[i] sprintf(fmt, $i); } } END { for (i in a) print a[i]; }'

Отрегулируйте FMT по мере необходимости. Для каждой входной строки он объединяет все поля в элемент массива. Обратите внимание, что конкатенация строк awk неявна: это происходит, когда вы пишете две вещи без оператора.

Пример ввода / вывода:

i       mark    accep   igna    utaal   bta
-22     -10     -10     -20     -10     -10
-21     -10     -10     -20     -10     -10
-20     -10     -10     -20     -10     -10
-19     -10     0       -10     -10     -10
-18     0       0       -10     0       0
-12     0       0       -10     0       0
-11     0       0       -10     0       0
-10     0       0       -10     0       0

выход:

       i     -22     -21     -20     -19     -18     -12     -11     -10
    mark     -10     -10     -10     -10       0       0       0       0
    accep    -10     -10     -10       0       0       0       0       0
    igna     -20     -20     -20     -10     -10     -10     -10     -10
    utaal    -10     -10     -10     -10       0       0       0       0
     bta     -10     -10     -10     -10       0       0       0       0
Питер Кордес
источник
-1

Самое простое, что вы можете сделать, это использовать cutдля вырезания полей, а затем использовать, trесли вы переносите строки в столбцы, заменяя символ новой строки на символ табуляции: http://www.gnu.org/software/coreutils/manual/ coreutils.html # тр-вызов

cat file.txt | cut -d':' | tr '\n' '\t'
Рудольф Олах
источник
Без списка полей cutвозвращает ошибку.
АРУ