2016-04-21 16 views
7

Giả sử chúng ta có hai tệp này:ống rất nhiều tác phẩm để stdin, giải nén cột đầu tiên, sau đó kết hợp những người trong một tập tin mới

$ cat ABC.txt 
ABC DEF 

$ cat PQR.txt 
PQR XTZ 

Và chúng tôi muốn tạo ra một tập tin mới với cột 1 của mỗi tệp. Điều này có thể đạt được bằng cách:

$ paste -d ' ' <(cut -d ' ' -f 1 ABC.txt) <(cut -d ' ' -f 1 PQR.txt) 
ABC PQR 

Nhưng tôi muốn sử dụng điều này với hàng tấn tệp trong đầu vào, không chỉ ABC.txt và PQR.TXT mà còn rất nhiều tệp. Làm thế nào chúng ta có thể khái quát tình hình này để vượt qua mỗi tập tin trong bộ sưu tập để cắt và sau đó vượt qua tất cả các kết quả đầu ra để dán (Tôi biết rằng điều này có thể được thực hiện tốt hơn với awk nhưng tôi muốn biết làm thế nào để giải quyết này sử dụng cách tiếp cận này).


Sửa 1

tôi đã phát hiện ra một cách bẩn bẩn để làm điều này:

$ str=''; for i in *.txt; \ 
      do str="${str} <(cut -d ' ' -f 1 ${i})"; \ 
      done ; \ 
    str="paste -d ' ' $str"; \ 
    eval $str 

Nhưng xin, giải phóng linh hồn của tôi với một câu trả lời mà không liên quan đến sắp Khoa học Máy tính Địa ngục.

Chỉnh sửa 2

Mỗi tệp có thể có n hàng, nếu điều này quan trọng.

+0

bạn chỉ có một hàng cho mỗi tệp? – karakfa

+0

Không, mỗi tệp có n hàng. – Dargor

Trả lời

5

Thay thế quá trình <(somecommand) không có đường ống thành tiêu chuẩn, nó thực sự mở một đường ống trên bộ mô tả tệp riêng biệt, ví dụ: 63, và vượt qua trong /dev/fd/63. Khi "tệp" này được mở, hạt nhân * sao chép fd thay vì mở một tệp thực.

Chúng ta có thể làm điều gì đó tương tự bằng cách mở một loạt các mô tả tập tin và sau đó chuyển chúng tới các lệnh:

# Start subshell so all files are automatically closed 
(
    fds=() 
    n=0 
    # Open a new fd for each process subtitution 
    for file in ./*.txt 
    do 
    exec {fds[n++]}< <(cut -d ' ' -f 1 "$file") 
    done 

    # fds now contain a list of fds like 12 14 
    # prepend "/dev/fd/" to all of them 
    parameters=("${fds[@]/#//dev/fd/}") 

    paste -d ' ' "${parameters[@]}" 
) 

{var}< file là cú pháp của bash để chuyển nhượng mô tả tập tin năng động. như var=4; exec 4< file; nhưng không cần phải mã hóa 4 và thay vào đó hãy bash chọn một bộ mô tả tệp miễn phí. exec mở nó trong trình bao hiện tại.

* Linux, FreeBSD, OpenBSD và XNU/OSX anyways. Đây không phải là POSIX, nhưng không phải là POS2, nhưng không phải là <(..)

+1

Được làm tốt; điều đáng nói đến là phương thức '{var}' định nghĩa các bộ mô tả tập tin yêu cầu Bash 4.1+. – mklement0

+0

Cảm ơn câu trả lời tuyệt vời! Bằng cách này tôi giả sử bạn có nghĩa là 'var = 4; exec 4 Dargor

1

Sau khi xem kỹ hơn, tôi thấy câu trả lời của @-người khác thật tuyệt vời, nhưng đây cũng là một cách bẩn thỉu khác gần giống như vậy.

eval "paste -d' ' "$(find *.txt -printf " <(cut -d' ' -f1 '%f')") 
1

Với không gian giới hạn tập tin đầu vào, và cung cấp ':' là một dấu phân cách an toàn, (tức là nếu không có dấu hai chấm trong dữ liệu), dán này để sed một liner hoạt động:

paste -d':' *.txt | sed 's/ [^:]*$//;s/ [^:]*:*/ /g;s/://g' 

(POSIX, không có eval, exec, bashISMS, subshells, hoặc loo ps.)

+0

@ that-other-guy's và câu trả lời của tôi là ~ 50x nhanh như thế này (thử nghiệm với 3 10.000.000 dòng .txt tập tin). – webb

+1

@webb, điều đó thật tuyệt, nhưng OP không nói rằng anh ấy đã thử nghiệm rất nhiều tệp nhỏ, thay vì một vài tệp lớn? Điểm chuẩn cho 10.000.000 tệp văn bản 3 dòng có thể phù hợp hơn. – agc

+1

điểm thú vị. câu trả lời của bạn nhanh hơn 50 lần đối với 2000 tệp đơn dòng, ví dụ: 40.000 tệp/giây so với 800 tệp/giây đối với câu trả lời @ của người khác và câu trả lời của tôi! ngoài ra, cả ba câu trả lời đều thất bại hoàn toàn, ví dụ: 3000 (hoặc nhiều hơn) tập tin. – webb

Các vấn đề liên quan