Transposisi baris dan kolom

18

Saya punya file dengan garis-garis seperti di bawah ini.

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

Bagaimana saya bisa mencapai ini?

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

Dens
sumber
tolong tolong jangan menggunakan awk, Anda mungkin juga menggulung solusi kustom dengan perl atau python atau bahasa pemrograman nyata atau menggunakan tr / cut dengan beberapa pass untuk mendapatkan apa yang Anda inginkan
Rudolf Olah

Jawaban:

14

Silakan lihat GNU datamash yang dapat digunakan seperti datamash transpose. Versi masa depan juga akan mendukung tabulasi silang (tabel pivot)

Pádraig Brady
sumber
9

Di luar menggulirkan solusi kustom untuk memindahkan baris dengan kolom dari baris perintah satu-satunya alat yang pernah saya lihat yang dapat melakukan ini adalah alat yang disebut ironisnya transpose.

Instalasi

Sayangnya itu tidak dalam repo apa pun sehingga Anda harus mengunduh dan mengompilasinya. Ini cukup mudah karena tidak memiliki pustaka tambahan yang tergantung padanya. Itu bisa dicapai seperti:

$ gcc transpose.c -o transpose

Pemakaian

Itu dapat menangani file teks langsung dengan mudah. Sebagai contoh:

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

Dapat ditransposisikan menggunakan perintah ini:

$ 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

Perintah ini adalah transposeuntuk mengubah posisi ( -t) dan pemisah bidang yang digunakan adalah spasi ( --fsep " ").

Contoh anda

Karena data sampel Anda berada dalam format yang sedikit lebih rumit, maka perlu ditangani dalam 2 fase. Pertama, kita perlu menerjemahkannya ke dalam format yang transposedapat menangani.

Menjalankan perintah ini, akan menempatkan data dalam format yang lebih ramah horizontal:

$ 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

Sekarang kita hanya perlu menghapus kejadian sekunder dari title1, title2, dll .:

$ 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

Sekarang dalam format yang transposedapat menangani. Perintah berikut akan melakukan seluruh transposisi:

$ 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
sumber
8

Anda dapat menggunakannya awkuntuk memproses data lalu pastedan columnuntuk memformatnya.

Di sini saya berasumsi title1hanya sebuah contoh dalam posting Anda, dan bahwa data tidak mengandung :kecuali sebagai pemisah antara header + data.

nmenandakan berapa banyak kolom untuk dicetak (harus cocok dengan tanda hubung 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")"

Jika Anda ingin membuatnya lebih fleksibel dan mudah dirawat, Anda bisa menulisnya sebagai skrip. Berikut adalah contoh menggunakan bash wrapper untuk awkdan disalurkan ke column. Dengan cara ini Anda juga bisa melakukan lebih banyak pengecekan data seperti mis. Memastikan tajuk benar di semua baris dll.

Digunakan biasanya sebagai:

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

Jika tajuk selalu lebih pendek maka data Anda juga dapat menyimpan lebar tajuk, lalu printfdengan %-*sdan lewati columnsemua.

#!/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
sumber
1
Jawaban bagus! @ JoelDavis dan saya telah meretas ini, tapi jawaban Anda luar biasa!
slm
7

Berikut cara cepat untuk meletakkan file ke dalam format yang Anda inginkan:

$ 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

Jika Anda ingin tajuk kolom:

$ 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

Cara kerja perintah ke-2

mencetak spanduk
grep -Ev "^$|title5" sample.txt | sed 's/:.*//' | sort -u | tr '\n' '\t';
menempatkan kembali setelah spanduk masuk
echo
mencetak deretan data
grep -Ev "^$|title5" a | sed 's/title[0-9]://g' | paste - - - -
slm
sumber
perintah paste hanya membuat pekerjaan saya selesai. terima kasih atas jawabannya ...
SK Venkat
3

Mungkin ada cara yang lebih ringkas untuk merumuskan ini tetapi ini tampaknya mencapai efek umum:

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

Banyak seddoa tidak terasa benar (dan saya cukup yakin juga bisa melakukan terjemahan baris baru) jadi itu mungkin bukan cara yang paling mudah untuk melakukannya. Selain itu, ini menghapus header calon, tetapi Anda dapat menghasilkan mereka secara manual setelah baris / bidang diformat dengan benar.

Jawaban yang lebih baik mungkin akan menyaring efek ke hanya menggunakan sedatau awkmelakukan ini sehingga Anda hanya memiliki satu hal yang terjadi pada suatu waktu. Tapi saya lelah jadi ini yang bisa saya kumpulkan.

Bratchley
sumber
Joel - Saya membuat kesalahan yang sama dan hanya menyadarinya, dia tidak ingin kolom title5 di output.
slm
Ah, berjalan dengan baik melalui awk pada akhirnya harus memperbaikinya. Tapi sepertinya Sukminder memposting solusi lengkap.
Bratchley
1

pastemungkin taruhan terbaik Anda. Anda dapat mengekstrak bit yang relevan dengan cut, grepdan awkseperti ini:

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

Jika kolom ke-5 harus dihilangkan, tambahkan awk 'NR%5'seperti ini:

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

Sekarang beri kolom dengan paste:

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

Keluaran:

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

Untuk bagian transpos saja, saya punya masalah yang sama baru-baru ini dan digunakan:

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

Sesuaikan fmt sesuai kebutuhan. Untuk setiap baris input, ini menggabungkan setiap bidang ke elemen array. Perhatikan bahwa gabungan string awk adalah implisit: itu terjadi ketika Anda menulis dua hal tanpa operator apa pun.

Sampel 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

keluaran:

       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
Peter Cordes
sumber
-1

Hal paling sederhana yang dapat Anda lakukan adalah menggunakan cutuntuk memotong bidang dan kemudian menggunakan trjika Anda mentransposisi baris ke kolom dengan mengganti karakter baris baru dengan karakter tab: http://www.gnu.org/software/coreutils/manual/ coreutils.html # tr-doa

cat file.txt | cut -d':' | tr '\n' '\t'
Rudolf Olah
sumber
Tanpa daftar bidang, cutmengembalikan kesalahan.
agc