Транспонування рядків і стовпців


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для транспонування ( -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

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")"

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 - - - -

команда paste просто зробила мою роботу. дякую за відповідь ...
SK Venkat


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зробити це, щоб у вас за часом відбувалося лише одне. Але я втомився, тому я це зміг скласти разом.


Джоель - я зробив ту саму помилку, і щойно це помітив, він не хоче, щоб колонка title5 у висновку.
slm

Ах, добре пробігшись через awk нарешті, слід це виправити. Але схоже на те, що Sukminder виклав повне рішення.
Братчлі

1

pasteце, мабуть, найкраща ставка. Ви можете отримати відповідні біти з cut, grepі awkце подобається:

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

Якщо 5-й стовпець слід усунути, додайте 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 # tr-виклик

cat file.txt | cut -d':' | tr '\n' '\t'

Без списку полів cutповертає помилку.
agc
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.