menggabungkan file teks dengan bijaksana

52

Saya punya dua file teks. Yang pertama memiliki konten:

Languages
Recursively enumerable
Regular

sedangkan yang kedua memiliki konten:

Minimal automaton
Turing machine
Finite

Saya ingin menggabungkan mereka menjadi satu kolom file-bijaksana. Jadi saya mencoba paste 1 2dan hasilnya adalah:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

Namun saya ingin memiliki kolom yang sejajar seperti

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Saya bertanya-tanya apakah mungkin untuk mencapai itu tanpa penanganan secara manual?


Ditambahkan:

Berikut adalah contoh lain, di mana metode Bruce hampir menangkapnya, kecuali beberapa ketidaksejajaran tentang yang saya heran mengapa?

$ 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)
Tim
sumber
3
Contoh terakhir, dengan misalignment, adalah doozy. Saya dapat menduplikasinya di Arch linux, pr (GNU coreutils) 8.12. Saya tidak dapat menduplikasinya pada Slackware tua (11.0) Saya juga memiliki sekitar: pr (GNU coreutils) 5.97. Masalahnya dengan karakter '-', dan itu dalam pr, bukan tempel.
Bruce Ediger
1
Saya mendapatkan hal yang sama dengan EM-DASH dengan keduanya prdan expand... columnsmenghindari masalah ini.
Peter.O
Saya telah menghasilkan output untuk sebagian besar jawaban yang berbeda kecuali untuk awk + paste , yang akan bergeser ke kanan sebagian besar kolom jika file kiri lebih pendek daripada t kanannya. Hal yang sama, dan banyak lagi, berlaku untuk 'tempelkan + kolom' yang juga memiliki masalah ini dengan garis-garis kosong di kolom kiri ... Jika Anda ingin melihat semua output bersama-sama. di sini adalah tautannya: paste.ubuntu.com/643692 Saya telah menggunakan 4 kolom.
Peter.O
Aku hanya diperhatikan sesuatu menyesatkan pada paste.ubuntu link yang ... Saya awalnya mengatur data untuk menguji skrip saya, (dan yang dipimpin untuk melakukan yang lain) ... jadi ladang yang mengatakan ➀ unicode may render oddly but the column count is ok pasti tidak tidak berlaku untuk wc-paste-prdan wc-paste-prMereka jangan tampilkan perbedaan jumlah kolom .. Yang lain ok.
Peter.O
1
@BruceEdiger: Masalah penyelarasan terjadi ketika karakter non-ASCII digunakan (dalam pertanyaannya, OP menggunakan tanda hubung (-) alih-alih karakter minus (-)), kemungkinan besar disebabkan oleh penanganan yang buruk atau tidak ada sama sekali oleh prmultibyte karakter di lokal saat ini (biasanya UTF8).
WhiteWinterWolf

Jawaban:

68

Anda hanya perlu columnperintah itu, dan katakan untuk menggunakan tab untuk memisahkan kolom

paste file1 file2 | column -s $'\t' -t

Untuk mengatasi kontroversi "sel kosong", kita hanya perlu -nopsi untuk 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

Halaman manual kolom saya menunjukkan -nadalah "Debian GNU / Linux extension." Sistem Fedora saya tidak menunjukkan masalah sel kosong: tampaknya berasal dari BSD dan halaman manual mengatakan "Versi 2.23 mengubah opsi -s menjadi non-serakah"

glenn jackman
sumber
4
Glenn: Anda adalah pahlawan saat ini! Saya tahu ada sesuatu seperti ini di sekitar, tetapi saya tidak bisa mengingatnya. Saya telah mengintai pertanyaan ini; menunggu kamu :) ... column, tentu saja; betapa jelasnya (di belakang) +1 ... Terima kasih ...
Peter.O
4
Saya baru saja memperhatikan bahwa column -s $'\t' -tmengabaikan sel - sel kosong , mengakibatkan semua sel berikutnya di sebelah kanannya (pada baris itu) bergerak ke kiri; yaitu, sebagai akibat dari baris kosong dalam file, atau lebih pendek ... :(
Peter.O
1
@masi, dikoreksi
glenn jackman
-n tidak bekerja di RHEL. Apakah ada alternatif?
Koshur
Saya akhirnya bisa berkomentar, jadi ingin dicatat bahwa saya sebelumnya menambahkan jawaban di bawah ini yang membahas masalah Peter.O dengan menjalankan sel kosong dengan menggunakan nulls.
techno
11

Anda mencari prperintah pesolek yang berguna :

paste file1 file2 | pr -t -e24

"-E24" adalah "memperluas tab berhenti ke 24 spasi". Untungnya, pastemenempatkan karakter tab di antara kolom, sehingga prdapat memperluasnya. Saya memilih 24 dengan menghitung karakter dalam "Recursively enumerable" dan menambahkan 2.

Bruce Ediger
sumber
Terima kasih! Apa yang dimaksud dengan "memperluas tab berhenti ke 24 spasi"?
Tim
Saya juga memperbarui dengan contoh di mana metode Anda hampir memaku kecuali sedikit ketidaksejajaran.
Tim
Secara tradisional "tabstops" mencapai setiap 8 spasi. "123TABabc" akan dicetak dengan karakter 'a' dengan lebar 8 karakter dari awal baris. Mengaturnya ke 24 akan menempatkan 'a' pada 24 char lebar dari awal baris.
Bruce Ediger
Anda mengatakan itu "-e24" adalah "memperluas tab berhenti untuk 24 ruang" , jadi mengapa tidak menggunakan expandperintah langsung: paste file1 file2 | expand -t 24?
WhiteWinterWolf
1
@Masi - jawaban saya serupa tetapi tidak terlalu rumit bahwa jawaban @ techno di bawah ini. Itu tidak memanggil sedjadi ada satu proses yang tidak berjalan. Menggunakan pryang merupakan perintah kuno, dating ke hari Unix SysV, saya pikir, jadi mungkin ada pada lebih banyak instalasi daripada expand. Singkatnya, itu hanya sekolah tua.
Bruce Ediger
9

Pembaruan : Ini dia skrip yang lebih sederhana (yang ada di akhir pertanyaan) untuk hasil tabulasi. Hanya lulus nama file untuk itu seperti yang Anda lakukan untuk paste... Menggunakan htmluntuk membuat frame, sehingga sangat tweakable. Itu memang melestarikan banyak ruang, dan perataan kolom dipertahankan ketika bertemu karakter unicode. Namun, cara editor atau pemirsa merender unicode adalah masalah lain sepenuhnya ...

┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐
│ 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 "$@" |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'

---

Sinopsis alat yang disajikan dalam jawaban (sejauh ini).
Saya sudah cukup dekat melihat mereka; inilah yang saya temukan:

paste# Alat ini umum untuk semua jawaban yang disajikan sejauh ini # Dapat menangani banyak file; karena itu banyak kolom ... Bagus! # Ini membatasi setiap kolom dengan Tab ... Bagus. # Outputnya tidak ditabulasi.

Semua alat di bawah ini menghapus pembatas ini! ... Buruk jika Anda membutuhkan pembatas.

column # Ini menghilangkan pembatas Tab, jadi pengidentifikasi bidang adalah murni oleh kolom yang tampaknya menangani dengan sangat baik .. Saya belum melihat sesuatu yang serba salah ... # Selain tidak memiliki pembatas yang unik, berfungsi dengan baik!

expand # Hanya memiliki pengaturan tab tunggal, sehingga tidak dapat diprediksi melebihi 2 kolom # Penjajaran kolom tidak akurat saat menangani unicode, dan menghapus pembatas Tab, jadi identifikasi bidang murni oleh perataan kolom

pr# Hanya memiliki pengaturan satu tab, sehingga tidak dapat diprediksi melebihi 2 kolom. # Penjajaran kolom tidak akurat saat menangani unicode, dan menghapus pembatas Tab, jadi identifikasi bidang murni oleh perataan kolom

Bagi saya, columnini solusi terbaik yang jelas sebagai one-liner .. Anda ingin pembatas, atau tabluasi ASCII-art dari file Anda, baca terus, jika tidak .. columnssangat bagus :) ...


Berikut ini adalah skrip yang mengambil numper file apa pun dan membuat presentasi tabulasi ASCII-art .. (Ingatlah bahwa unicode mungkin tidak merender sesuai lebar yang diharapkan, mis. ௵ yang merupakan karakter tunggal. Ini sangat berbeda dengan kolom nomor menjadi salah, seperti halnya pada beberapa utilitas yang disebutkan di atas.) ... Keluaran skrip, yang ditunjukkan di bawah, berasal dari 4 file input, bernama 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

Inilah jawaban asli saya (dipangkas sedikit sebagai pengganti skrip di atas)

Menggunakan wcuntuk mendapatkan lebar kolom, dan sedke kanan pad dengan karakter yang terlihat. (hanya untuk contoh ini) ... dan kemudian pasteuntuk bergabung dengan dua kolom dengan karakter Tab ...

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

Jika Anda ingin mengisi kolom kanan:

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...........
Peter.O
sumber
Terima kasih! Anda telah melakukan banyak pekerjaan. Itu luar biasa.
Tim
5

Kamu hampir sampai. pastemenempatkan karakter tab di antara setiap kolom, jadi yang perlu Anda lakukan adalah memperluas tab. (Saya menganggap file Anda tidak mengandung tab.) Anda perlu menentukan lebar kolom kiri. Dengan (cukup baru) utilitas GNU, wc -Lmenunjukkan panjang garis terpanjang. Pada sistem lain, buat pass pertama dengan awk. Ini +1adalah jumlah ruang kosong yang Anda inginkan di antara kolom.

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}')

Jika Anda memiliki utilitas kolom BSD, Anda dapat menggunakannya untuk menentukan lebar kolom dan memperluas tab sekaligus. ( adalah karakter tab literal; di bawah bash / ksh / zsh Anda dapat menggunakan $'\t'sebagai gantinya, dan dalam shell apa pun yang dapat Anda gunakan "$(printf '\t')".)

paste left.txt right.txt | column -s '␉' -t
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Dalam versi saya wc, perintahnya harus: wc -L <left.txt... karena, ketika nama file ditetapkan sebagai baris perintah arg , namanya adalah keluaran ke stdout
Peter.O
4

Ini multi-langkah, jadi ini tidak optimal, tapi begini saja.

1) Temukan panjang garis terpanjang di file1.txt.

while read line
do
echo ${#line}
done < file1.txt | sort -n | tail -1

Dengan contoh Anda, garis terpanjang adalah 22.

2) Gunakan awk untuk membalut file1.txt, melapisi setiap baris kurang dari 22 karakter hingga 22 dengan printfpernyataan.

awk 'FS="---" {printf "%-22s\n", $1}' < file1.txt > file1-pad.txt

Catatan: Untuk FS, gunakan string yang tidak ada di file1.txt.

3) Gunakan tempel seperti yang Anda lakukan sebelumnya.

$ paste file1-pad.txt file2.txt
Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Jika ini adalah sesuatu yang sering Anda lakukan, ini dapat dengan mudah diubah menjadi skrip.

bahamat
sumber
Dalam kode Anda untuk menemukan garis terpanjang, Anda perlu while IFS= read -r line, jika tidak, shell akan memotong spasi dan backslash. Tetapi shell bukanlah alat terbaik untuk pekerjaan itu; versi terbaru dari coreutils GNU telah wc -L(lihat jawaban fred), atau Anda dapat menggunakan awk: awk 'n<length {n=length} END {print +n}'.
Gilles 'SANGAT berhenti menjadi jahat'
4

Saya tidak dapat mengomentari jawaban glenn jackman, jadi saya menambahkan ini untuk mengatasi masalah sel kosong yang dicatat Peter.O. Menambahkan null char sebelum setiap tab menghilangkan proses pembatas yang diperlakukan sebagai satu break dan mengatasi masalah tersebut. (Saya awalnya menggunakan spasi, tetapi menggunakan null char menghilangkan ruang ekstra di antara kolom.)

paste file1 file2 | sed 's/\t/\0\t/g' | column -s $'\t' -t

Jika null char menyebabkan masalah karena berbagai alasan, cobalah:

paste file1 file2 | sed 's/\t/ \t/g' | column -s $'\t' -t

atau

paste file1 file2 | sed $'s/\t/ \t/g' | column -s $'\t' -t

Keduanya seddan columntampaknya bervariasi dalam implementasi lintas rasa dan versi Unix / Linux, terutama BSD (dan Mac OS X) vs GNU / Linux.

techno
sumber
Perintah sed itu tampaknya tidak melakukan apa-apa. Saya mengganti perintah kolom dengan od -cdan saya tidak melihat byte nol. Ini di centos dan ubuntu.
glenn jackman
1
Ini bekerja untuk saya di RedHat EL4. Baik sed dan kolom tampaknya bervariasi dari waktu ke waktu dan sistem. Di Ubuntu 14.4 menggunakan \0tidak berfungsi sebagai nullsed, tetapi \x0berhasil. Namun, kemudian kolom memberi line too longkesalahan. Hal paling sederhana tampaknya menggunakan ruang dan hidup dengan karakter ekstra.
techno
0

Membangun dari jawaban bahamat : ini bisa dilakukan sepenuhnya awk, membaca file hanya sekali dan tidak membuat file sementara. Untuk mengatasi masalah seperti yang dinyatakan, lakukan

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

Seperti halnya banyak awkskrip sejenis ini, yang pertama di atas berbunyi file1, menyimpan semua data dalam savearray dan secara bersamaan menghitung panjang garis maksimum. Kemudian ia membaca file2 dan mencetak data yang disimpan ( file1) berdampingan dengan data saat ini ( file2). Akhirnya, jika file1lebih panjang dari file2(memiliki lebih banyak baris), kami mencetak beberapa baris terakhir file1 (yang tidak ada baris yang sesuai di kolom kedua).

Mengenai printfformat:

  • "%-nns"mencetak string yang dibenarkan nnlebar dalam karakter bidang .
  • "%-*s", nnmelakukan hal yang sama - *memberitahu untuk mengambil lebar bidang dari parameter berikutnya.
  • Dengan menggunakan for , kita mendapatkan dua spasi di antara kolom. Tentunya bisa disesuaikan.maxlength+2nn+2

Script di atas hanya berfungsi untuk dua file. Itu sepele dapat dimodifikasi untuk menangani tiga file, atau untuk menangani empat file, dll., Tetapi ini akan membosankan dan dibiarkan sebagai latihan. Namun, ternyata tidak sulit untuk memodifikasi untuk menangani sejumlah dari file:

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*

Ini sangat mirip dengan skrip pertama saya, kecuali

  • Itu berubah max_lengthmenjadi sebuah array.
  • Itu berubah max_FNRmenjadi sebuah array.
  • Itu berubah savemenjadi array dua dimensi.
  • Itu membaca semua file, menyimpan semua konten. Kemudian ia menulis semua output dari ENDblok.
G-Man Mengatakan 'Reinstate Monica'
sumber
Saya tahu bahwa pertanyaan ini sudah lama; Saya baru saja menemukan itu. Saya setuju itu pasteadalah solusi terbaik; secara khusus, glenn jackman paste file1 file2 | column -s $'\t' -t. Tetapi saya pikir akan menyenangkan untuk mencoba memperbaiki awkpendekatannya.
G-Man Mengatakan 'Reinstate Monica'