Chuyển hàng và cột


18

Tôi có một tập tin với các dòng như dưới đây.

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

Làm thế nào tôi có thể đạt được điều này?

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


xin vui lòng đừng sử dụng awk, bạn cũng có thể cuộn một giải pháp tùy chỉnh với perl hoặc python hoặc ngôn ngữ lập trình thực hoặc sử dụng tr / cut với nhiều lượt để đạt được những gì bạn muốn
Rudolf Olah

Câu trả lời:


14

Hãy xem dữ liệu GNU có thể được sử dụng như thế nào datamash transpose. Một phiên bản trong tương lai cũng sẽ hỗ trợ lập bảng chéo (bảng xoay vòng)


9

Ngoài việc đưa ra một giải pháp tùy chỉnh để hoán chuyển các hàng với các cột từ một dòng lệnh, công cụ duy nhất tôi từng thấy có thể làm điều này là một công cụ gọi là trớ trêu transpose.

Cài đặt

Thật không may, nó không có trong bất kỳ repo nào vì vậy bạn sẽ cần phải tải xuống và biên dịch nó. Điều này khá đơn giản vì nó không có thư viện bổ sung mà nó phụ thuộc vào. Nó có thể được thực hiện như vậy:

$ gcc transpose.c -o transpose

Sử dụng

Nó có thể xử lý các tập tin văn bản đơn giản dễ dàng. Ví dụ:

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

Có thể được chuyển đổi bằng lệnh này:

$ 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

Lệnh này là transposetranspose ( -t) và dấu tách trường sẽ sử dụng là khoảng trắng ( --fsep " ").

Ví dụ của bạn

Vì dữ liệu mẫu của bạn có định dạng phức tạp hơn một chút, nó cần được xử lý theo 2 giai đoạn. Đầu tiên chúng ta cần dịch nó sang một định dạng transposecó thể xử lý.

Chạy lệnh này, sẽ đặt dữ liệu theo định dạng thân thiện hơn theo chiều ngang:

$ 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

Bây giờ chúng ta chỉ cần loại bỏ các lần xuất hiện thứ cấp của title1, title2, v.v.:

$ 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

Bây giờ nó ở một định dạng transposecó thể đối phó. Lệnh sau sẽ thực hiện toàn bộ chuyển vị:

$ 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

Bạn có thể sử dụng awkđể xử lý dữ liệu sau đó pastecolumnđịnh dạng dữ liệu .

Ở đây tôi giả sử title1chỉ là một ví dụ trong bài đăng của bạn và dữ liệu đó không chứa :ngoại trừ là dấu phân cách giữa dữ liệu tiêu đề +.

nbiểu thị có bao nhiêu cột cần in (nên khớp với dấu gạch ngang 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")"

Nếu bạn muốn làm cho nó linh hoạt hơn và dễ bảo trì, bạn có thể viết nó dưới dạng một kịch bản. Dưới đây là một ví dụ sử dụng bash Wrapper cho awkvà đường ống đến column. Bằng cách này, bạn cũng có thể thực hiện nhiều kiểm tra dữ liệu hơn, ví dụ như đảm bảo các tiêu đề chính xác trong tất cả các hàng, v.v.

Được sử dụng điển hình như:

$ ./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

Nếu tiêu đề luôn luôn là dữ liệu ngắn hơn thì bạn có thể cũng tiết kiệm độ rộng tiêu đề, sau đó printfvới %-*svà bỏ qua columntất cả cùng nhau.

#!/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
Câu trả lời tốt đẹp! @JoelDavis và tôi đã hack về điều này, nhưng câu trả lời của bạn thật tuyệt vời!
slm

7

Đây là một cách nhanh chóng để đưa tệp vào định dạng bạn muốn:

$ 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

Nếu bạn muốn các tiêu đề cột:

$ 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

Cách lệnh thứ 2 hoạt động

in banner
grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t';
đặt trở lại sau khi banner
echo
in các hàng dữ liệu
grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -

lệnh dán chỉ đơn giản là thực hiện công việc của tôi. cảm ơn vì câu trả lời ...
SK Venkat


3

Có lẽ có một cách ngắn gọn hơn để xây dựng điều này nhưng điều này dường như hoàn thành hiệu quả chung:

[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 ~]$ 

Nhiều sedlời mời không cảm thấy đúng (và tôi khá chắc chắn sed cũng có thể thực hiện dịch thuật dòng mới) nên có lẽ đó không phải là cách đơn giản nhất để thực hiện. Ngoài ra, điều này loại bỏ các tiêu đề sẽ là, nhưng bạn có thể tạo các tiêu đề theo cách thủ công một khi bạn có các hàng / trường được định dạng đúng.

Một câu trả lời tốt hơn có lẽ sẽ chắt lọc hiệu ứng đó xuống chỉ bằng cách sử dụng sedhoặc awklàm điều này để bạn chỉ có một điều đang diễn ra tại một thời điểm. Nhưng tôi mệt nên đây là thứ tôi có thể ghép lại.


Joel - Tôi đã mắc lỗi tương tự và chỉ nhận thấy điều đó, anh ấy không muốn cột title5 ở đầu ra.
slm

Ah, cuối cùng cũng chạy qua awk nên sửa nó. Nhưng có vẻ như Sukminder đã đăng một giải pháp hoàn chỉnh.
Bratchley

1

pastecó lẽ là đặt cược tốt nhất của bạn Bạn có thể trích xuất các bit có liên quan với cut, grepawknhư thế này:

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

Nếu cột thứ 5 cần được loại bỏ, hãy thêm vào awk 'NR%5'như sau:

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

Bây giờ cột với paste:

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

Đầu ra:

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

0

Đối với phần chuyển đổi, tôi đã có một vấn đề tương tự gần đây và được sử dụng:

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]; }'

Điều chỉnh fmt khi cần thiết. Đối với mỗi dòng đầu vào, nó nối từng trường vào một phần tử mảng. Lưu ý rằng nối chuỗi awk là ẩn: nó xảy ra khi bạn viết hai thứ mà không cần bất kỳ toán tử nào.

Mẫu I / O:

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

đầu ra:

       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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.