kết hợp các tập tin văn bản cột khôn ngoan


52

Tôi có hai tập tin văn bản. Cái đầu tiên có nội dung:

Languages
Recursively enumerable
Regular

trong khi cái thứ hai có nội dung:

Minimal automaton
Turing machine
Finite

Tôi muốn kết hợp chúng thành một cột tập tin. Vì vậy, tôi đã thử paste 1 2và đầu ra của nó là:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

Tuy nhiên tôi muốn có các cột được căn chỉnh tốt như

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Tôi đã tự hỏi nếu nó sẽ có thể đạt được điều đó mà không cần xử lý bằng tay?


Thêm:

Đây là một ví dụ khác, trong đó phương pháp Bruce gần như đóng đinh nó, ngoại trừ một số sai lệch nhỏ về điều mà tôi tự hỏi tại sao?

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

3
Đó là ví dụ cuối cùng, với sự sai lệch, là một doozy. Tôi có thể sao chép nó trên Arch linux, pr (GNU coreutils) 8.12. Tôi không thể sao chép nó trên Slackware cũ (11.0) Tôi cũng có khoảng: pr (GNU coreutils) 5.97. Vấn đề là ở ký tự '-' và nó ở dạng pr, không dán.
Bruce Ediger

1
Tôi nhận được điều tương tự với EM-DASH với cả hai prexpand... columnstránh vấn đề này.
Peter.O

Tôi đã tạo đầu ra cho hầu hết các câu trả lời khác nhau ngoại trừ awk + paste , phần này sẽ dịch chuyển sang trái hầu hết các cột bên phải nếu một tệp bên trái ngắn hơn bất kỳ t bên phải của nó. Tương tự, và hơn thế nữa, áp dụng cho 'dán + cột' cũng có vấn đề này với các dòng trống ở (các) cột bên trái ... Nếu bạn muốn xem tất cả các đầu ra cùng nhau. đây là đường dẫn: paste.ubfox.com/643692 Tôi đã sử dụng 4 cột.
Peter.O

Tôi chỉ chú ý một cái gì đó gây hiểu lầm trên paste.ubuntu liên kết ... Tôi ban đầu được thiết lập dữ liệu lên để thử nghiệm các kịch bản của tôi, (và dẫn vào làm những người khác) ... do đó các trường mà nói ➀ unicode may render oddly but the column count is ok chắc chắn không không áp dụng đối với wc-paste-prwc-paste-prHọ làm hiển thị số lượng cột khác nhau .. Những người khác là ok.
Peter.O

1
@BruceEdiger: Sự cố căn chỉnh xảy ra khi các ký tự không phải ASCII được sử dụng (trong câu hỏi của anh ấy, OP đã sử dụng dấu gạch ngang (-) thay vì dấu trừ (-)), rất có thể là do xử lý prđa yếu tố hoặc không được xử lý bởi đa nhân các ký tự trong miền địa phương hiện tại (thường là UTF8).
WhiteWinterWolf

Câu trả lời:


68

Bạn chỉ cần columnlệnh và bảo nó sử dụng các tab để tách các cột

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

Để giải quyết tranh cãi "ô trống", chúng ta chỉ cần -ntùy chọn 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

Trang man cột của tôi chỉ ra -nlà "phần mở rộng Debian GNU / Linux." Hệ thống Fedora của tôi không thể hiện vấn đề ô trống: có vẻ như nó bắt nguồn từ BSD và trang man nói "Phiên bản 2.23 đã thay đổi tùy chọn -s thành không tham lam"


4
glenn: Bạn là người hùng của giờ! Tôi biết có một cái gì đó như thế này xung quanh, nhưng tôi không thể nhớ nó. Tôi đã ẩn giấu câu hỏi này; chờ đợi bạn :) ... column, tất nhiên; rõ ràng như thế nào (trong nhận thức muộn) +1 ... Cảm ơn ...
Peter.O

4
Tôi chỉ nhận thấy rằng column -s $'\t' -tbỏ qua các ô trống , dẫn đến tất cả các ô tiếp theo ở bên phải của nó (trên dòng đó) để di chuyển sang bên trái; tức là, do kết quả của một dòng trống trong một tệp, hoặc nó ngắn hơn ... :(
Peter.O

1
@masi, sửa chữa
glenn Jackman

-n không hoạt động trong RHEL. Có một sự thay thế?
Koshur

Cuối cùng tôi cũng có thể nhận xét, vì vậy muốn lưu ý rằng trước đây tôi đã thêm một câu trả lời bên dưới nhằm giải quyết vấn đề của Peter.O với việc chạy các ô trống bằng cách sử dụng null.
kỹ thuật

11

Bạn đang tìm kiếm prlệnh dandy tiện dụng :

paste file1 file2 | pr -t -e24

"-E24" là "tab mở rộng dừng thành 24 khoảng trắng". May mắn thay, pasteđặt một ký tự tab giữa các cột, vì vậy prcó thể mở rộng nó. Tôi đã chọn 24 bằng cách đếm các ký tự trong "Số đệ quy" và thêm 2.


Cảm ơn! "Tab mở rộng dừng lại thành 24 dấu cách" nghĩa là gì?
Tim

Tôi cũng cập nhật với một ví dụ trong đó phương pháp của bạn gần như đóng đinh nó ngoại trừ một sai lệch nhỏ.
Tim

Theo truyền thống, "tabstops" đạt được 8 khoảng trống. "123TABabc" sẽ được in ra với độ rộng 8 ký tự 'a' từ đầu dòng. Đặt nó thành 24 sẽ đặt 'a' ở độ rộng 24 char từ đầu dòng.
Bruce Ediger

Bạn nói "-e24" là "tab mở rộng dừng thành 24 khoảng trắng" , vậy tại sao không sử dụng expandlệnh trực tiếp : paste file1 file2 | expand -t 24?
WhiteWinterWolf

1
@Masi - Câu trả lời của tôi tương tự nhưng ít phức tạp hơn mà câu trả lời của @ techno bên dưới. Nó không gọi sednên có một quá trình không chạy. Nó prnghĩ rằng nó sử dụng một lệnh cổ xưa, có niên đại từ Unix SysV, vì vậy nó có thể tồn tại trên nhiều cài đặt hơn expand. Đó chỉ là trường học cũ.
Bruce Ediger

9

Cập nhật : Ở đây tôi là một tập lệnh đơn giản hơn nhiều (tập lệnh ở cuối câu hỏi) cho đầu ra được lập bảng. Chỉ cần truyền tên tệp cho nó như bạn muốn paste... Nó sử dụng htmlđể tạo khung, vì vậy nó có thể điều chỉnh được. Nó bảo tồn nhiều khoảng trắng và căn chỉnh cột được giữ nguyên khi gặp các ký tự unicode. Tuy nhiên, cách trình soạn thảo hoặc trình xem kết xuất unicode lại là một vấn đề khác hoàn toàn ...

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

---

Một bản tóm tắt của các công cụ được trình bày trong các câu trả lời (cho đến nay).
Tôi đã có một cái nhìn khá gần với họ; đây là những gì tôi đã tìm thấy:

paste# Công cụ này phổ biến cho tất cả các câu trả lời được trình bày cho đến nay # Nó có thể xử lý nhiều tệp; do đó nhiều cột ... Tốt! # Nó phân định từng cột bằng một Tab ... Tốt. # Đầu ra của nó không được lập bảng.

Tất cả các công cụ dưới đây đều loại bỏ dấu phân cách này! ... Xấu nếu bạn cần một dấu phân cách.

column # Nó loại bỏ dấu phân cách Tab, do đó, nhận dạng trường hoàn toàn bằng các cột mà nó dường như xử lý khá tốt .. Tôi không phát hiện ra bất cứ điều gì tồi tệ ... # Ngoài việc không có một dấu phân cách duy nhất, nó hoạt động tốt!

expand # Chỉ có một cài đặt tab duy nhất, do đó không thể đoán trước ngoài 2 cột # Việc căn chỉnh các cột không chính xác khi xử lý unicode và nó loại bỏ dấu phân cách Tab, do đó, nhận dạng trường hoàn toàn bằng cách căn chỉnh cột

pr# Chỉ có một cài đặt tab duy nhất, vì vậy không thể đoán trước ngoài 2 cột. # Căn chỉnh các cột không chính xác khi xử lý unicode và nó loại bỏ dấu phân cách Tab, do đó, nhận dạng trường hoàn toàn bằng căn chỉnh cột

Đối với tôi, columnđó là soluton rõ ràng nhất với tư cách là một lớp lót .. Bạn muốn hoặc là dấu phân cách hoặc một biểu đồ nghệ thuật ASCII của các tệp của bạn, đọc, nếu không .. columnslà khá tốt :) ...


Đây là một tập lệnh lấy bất kỳ số tập tin nào và tạo một bản trình bày được lập bảng theo nghệ thuật ASCII .. (Hãy nhớ rằng unicode có thể không hiển thị theo chiều rộng dự kiến, ví dụ: là một ký tự đơn. Điều này khá khác với cột các số bị sai, như trường hợp trong một số tiện ích được đề cập ở trên.) ... Đầu ra của tập lệnh, được hiển thị bên dưới, là từ 4 tệp đầu vào, được đặt tên là 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

Đây là câu trả lời ban đầu của tôi (cắt bớt một chút thay cho kịch bản trên)

Sử dụng wcđể có được độ rộng cột, và sedđể pad đúng với một có thể nhìn thấy nhân vật .(chỉ dành riêng cho ví dụ này) ... và sau đó pastetham gia hai cột với một Tab char ...

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

Nếu bạn muốn điền vào cột bên phải:

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

Cảm ơn! Bạn đã làm khá nhiều việc. Thật đáng kinh ngạc.
Tim

5

Bạn đã gần tới. pasteđặt một ký tự tab giữa mỗi cột, vì vậy tất cả những gì bạn cần làm là mở rộng các tab. (Tôi cho rằng các tệp của bạn không chứa các tab.) Bạn cần xác định độ rộng của cột bên trái. Với các tiện ích GNU (đủ gần đây), wc -Lhiển thị độ dài của dòng dài nhất. Trên các hệ thống khác, thực hiện một lượt đi đầu tiên với awk. Số +1lượng khoảng trống bạn muốn giữa các cột.

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

Nếu bạn có tiện ích cột BSD, bạn có thể sử dụng nó để xác định độ rộng cột và mở rộng các tab trong một lần. ( là một ký tự tab theo nghĩa đen; dưới bash / ksh / zsh bạn có thể sử dụng $'\t'thay thế và trong bất kỳ shell nào bạn có thể sử dụng "$(printf '\t')".)

paste left.txt right.txt | column -s '␉' -t

Trong phiên bản của tôi wc, lệnh cần phải là: wc -L <left.txt... bởi vì, khi tên tệp được tăng tốc dưới dạng dòng lệnh arg , tên của nó được xuất thành thiết bị xuất chuẩn
Peter.O

4

Đây là nhiều bước, vì vậy nó không tối ưu, nhưng ở đây đi.

1) Tìm chiều dài của dòng dài nhất trong file1.txt.

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

Với ví dụ của bạn, dòng dài nhất là 22.

2) Sử dụng awk để đệm file1.txt, đệm mỗi dòng nhỏ hơn 22 ký tự cho đến 22 ký tự printf.

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

Lưu ý: Đối với FS, sử dụng chuỗi không tồn tại file1.txt.

3) Sử dụng dán như bạn đã làm trước đây.

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

Nếu đây là việc bạn làm thường xuyên, điều này có thể dễ dàng biến thành kịch bản.


Trong mã của bạn để tìm dòng dài nhất, bạn cần while IFS= read -r line, nếu không, trình bao sẽ xáo trộn khoảng trắng và dấu gạch chéo ngược. Nhưng vỏ không phải là công cụ tốt nhất cho công việc đó; các phiên bản gần đây của lõi core GNU có wc -L(xem câu trả lời của fred) hoặc bạn có thể sử dụng awk : awk 'n<length {n=length} END {print +n}'.
Gilles 'SO- ngừng trở nên xấu xa'

4

Tôi không thể nhận xét về câu trả lời của glenn jackman, vì vậy tôi đang thêm điều này để giải quyết vấn đề về các ô trống mà Peter.O lưu ý. Thêm một char char trước mỗi tab sẽ giúp loại bỏ các dấu phân cách được coi là một dấu ngắt đơn và giải quyết vấn đề. (Ban đầu tôi sử dụng khoảng trắng, nhưng sử dụng null char sẽ loại bỏ khoảng trắng thừa giữa các cột.)

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

Nếu null char gây ra sự cố vì nhiều lý do, hãy thử:

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

hoặc là

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

Cả hai sedcolumndường như khác nhau trong việc triển khai trên các hương vị và phiên bản của Unix / Linux, đặc biệt là BSD (và Mac OS X) so với GNU / Linux.


Lệnh sed đó dường như không làm gì cả. Tôi thay thế lệnh cột bằng od -cvà tôi không thấy bất kỳ byte null nào. Đây là trên centos và ubfox.
glenn jackman

1
Điều này làm việc cho tôi trong RedHat EL4. Cả sed và cột dường như thay đổi theo thời gian và hệ thống. Trong Ubuntu 14.4, việc sử dụng \0không hoạt động như một nullsed, nhưng \x0đã làm. Tuy nhiên, sau đó cột đã đưa ra một line too longlỗi. Điều đơn giản nhất dường như là sử dụng một không gian và sống với nhân vật phụ.
kỹ thuật

0

Dựa trên câu trả lời của bahamat : điều này có thể được thực hiện hoàn toàn awk, chỉ đọc các tệp một lần và không tạo bất kỳ tệp tạm thời nào. Để giải quyết vấn đề như đã nêu, hãy làm

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

Cũng như nhiều awktập lệnh của ilk này, lần đầu tiên ở trên đọc file1, lưu tất cả dữ liệu trong savemảng và đồng thời tính toán độ dài dòng tối đa. Sau đó, nó đọc file2 và in các file1dữ liệu được lưu ( ) song song với file2dữ liệu hiện tại ( ). Cuối cùng, nếu file1dài hơn file2(có nhiều dòng), chúng tôi sẽ in một vài dòng cuối cùng file1 (những dòng không có dòng tương ứng trong cột thứ hai).

Về printfđịnh dạng:

  • "%-nns"in một chuỗi trái - được chứng minh trong một nnký tự trường rộng.
  • "%-*s", nnthực hiện điều tương tự - *bảo nó lấy độ rộng trường từ tham số tiếp theo.
  • Bằng cách sử dụng cho , chúng ta có được hai khoảng trắng giữa các cột. Rõ ràng là có thể được điều chỉnh.maxlength+2nn+2

Kịch bản trên chỉ hoạt động cho hai tập tin. Nó có thể được sửa đổi một cách tầm thường để xử lý ba tệp hoặc để xử lý bốn tệp, v.v., nhưng điều này sẽ rất tẻ nhạt và bị bỏ lại như một bài tập. Tuy nhiên, nó quay ra không phải là khó để sửa đổi nó để xử lý bất kỳ số lượng các tập tin:

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*

Điều này rất giống với kịch bản đầu tiên của tôi, ngoại trừ

  • Nó biến max_lengththành một mảng.
  • Nó biến max_FNRthành một mảng.
  • Nó biến savethành một mảng hai chiều.
  • Nó đọc tất cả các tập tin, lưu tất cả các nội dung. Sau đó, nó viết ra tất cả các đầu ra từ ENDkhối.

Tôi biết rằng câu hỏi này đã cũ; Tôi chỉ vấp phải nó. Tôi đồng ý rằng đó pastelà giải pháp tốt nhất; đặc biệt, glenn jackman's paste file1 file2 | column -s $'\t' -t. Nhưng tôi nghĩ sẽ rất vui nếu cố gắng cải thiện awkphương pháp này.
G-Man nói 'Phục hồi Monica'
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.