Nhận văn bản từ điểm đánh dấu cuối cùng đến EOF trong POSIX.2


8

Tôi có một văn bản với các dòng đánh dấu như:

aaa
---
bbb
---
ccc

Tôi cần nhận một văn bản từ điểm đánh dấu cuối cùng (không bao gồm) đến EOF. Trong trường hợp này nó sẽ là

ccc

Có một cách thanh lịch trong POSIX.2? Ngay bây giờ tôi sử dụng hai lần chạy: lần đầu tiên nlgreplần xuất hiện cuối cùng với số dòng tương ứng. Sau đó, tôi trích xuất số dòng và sử dụng sedđể trích xuất đoạn trong câu hỏi.

Các phân đoạn văn bản có thể khá lớn, vì vậy tôi ngại sử dụng một số phương pháp thêm văn bản như chúng tôi thêm văn bản vào bộ đệm, nếu gặp phải điểm đánh dấu, chúng tôi sẽ làm trống bộ đệm, để tại EOF, chúng tôi có đoạn cuối cùng trong đệm.

Câu trả lời:


6

Trừ khi các phân khúc của bạn thực sự rất lớn (như trong: bạn thực sự không thể tiết kiệm được nhiều RAM như vậy, có lẽ vì đây là một hệ thống nhúng nhỏ kiểm soát một hệ thống tệp lớn), một lượt thực sự là cách tiếp cận tốt hơn. Không chỉ vì nó sẽ nhanh hơn, mà quan trọng nhất là vì nó cho phép nguồn phát thành luồng, từ đó mọi dữ liệu đọc và không lưu sẽ bị mất. Đây thực sự là một công việc cho awk, mặc dù sed cũng có thể làm điều đó.

sed -n -e 's/^---$//' -e 't a' \
       -e 'H' -e '$g' -e '$s/^\n//' -e '$p' -e 'b' \
       -e ':a' -e 'h'              # you are not expected to understand this
awk '{if (/^---$/) {chunk=""}      # separator ==> start new chunk
      else {chunk=chunk $0 RS}}    # append line to chunk
     END {printf "%s", chunk}'     # print last chunk (without adding a newline)

Nếu bạn phải sử dụng cách tiếp cận hai lượt, hãy xác định độ lệch dòng của dấu tách cuối cùng và in từ đó. Hoặc xác định bù byte và in từ đó.

</input/file tail -n +$((1 + $(</input/file         # print the last N lines, where N=…
                               grep -n -e '---' |   # list separator line numbers
                               tail -n 1 |          # take the last one
                               cut -d ':' -f 1) ))  # retain only line number
</input/file tail -n +$(</input/file awk '/^---$/ {n=NR+1} END {print n}')
</input/file tail -c +$(</input/file LC_CTYPE=C awk '
    {pos+=length($0 RS)}        # pos contains the current byte offset in the file
    /^---$/ {last=pos}          # last contains the byte offset after the last separator
    END {print last+1}          # print characters from last (+1 because tail counts from 1)
')

Phụ lục: Nếu bạn có nhiều hơn POSIX, thì đây là phiên bản một lượt đơn giản dựa trên tiện ích mở rộng chung để awk cho phép dấu tách bản ghi RSlà biểu thức chính quy (POSIX chỉ cho phép một ký tự). Điều đó không hoàn toàn chính xác: nếu tệp kết thúc bằng dấu tách bản ghi, nó sẽ in đoạn mã trước dấu tách bản ghi cuối cùng thay vì bản ghi trống. Phiên bản thứ hai sử dụng RTđể tránh lỗi đó, nhưng RTdành riêng cho GNU awk.

awk -vRS='(^|\n)---+($|\n)' 'END{printf $0}'
gawk -vRS='(^|\n)---+($|\n)' 'END{if (RT == "") printf $0}'

@Gilles: sedđang hoạt động tốt, nhưng tôi không thể lấy awkví dụ để chạy; nó bị treo ... và tôi gặp lỗi trong ví dụ thứ 3: cut -f ':' -t 1 ... cut: tùy chọn không hợp lệ - 't'
Peter.O

@ fred.bear: Tôi không biết điều đó đã xảy ra như thế nào - Tôi đã thử tất cả các đoạn mã của mình, nhưng bằng cách nào đó đã làm rối tung chỉnh sửa sau sao chép-dán trên cutví dụ. Tôi thấy không có gì sai với awkví dụ, bạn đang sử dụng phiên bản awk nào, và đầu vào thử nghiệm của bạn là gì?
Gilles 'SO- đừng trở nên xấu xa'

... thực sự là awkphiên bản đang hoạt động .. nó mất rất nhiều thời gian trên một tệp lớn .. sedphiên bản đã xử lý cùng một tệp trong 0.470s .. Dữ liệu thử nghiệm của tôi rất nặng ... chỉ có hai khối với một mình '---' ba dòng từ cuối 1 triệu dòng ...
Peter.O

@Gilles .. (Tôi nghĩ rằng tôi nên dừng thử nghiệm tại 03:00. Tôi bằng cách nào đó đã thử nghiệm cả ba "hai đường chuyền" awks như một đơn vị duy nhất :( ... bây giờ tôi đã kiểm tra từng cá nhân và điều thứ hai là rất nhanh tại 0,204 giây ... Howerver, chỉ xuất ra awk "hai lần" đầu tiên: " (đầu vào tiêu chuẩn) " (-l dường như là thủ phạm) ... như đối với awk "hai vượt qua" thứ ba, tôi không 'không xuất bất cứ thứ gì ... nhưng "hai lần" thứ hai là nhanh nhất trong tất cả các phương thức được trình bày (POSIX hay nói cách khác :) ...
Peter.O

@ fred.bear: Đã sửa và đã sửa. QA của tôi không tốt cho những đoạn ngắn này - Tôi thường sao chép-dán từ một dòng lệnh, định dạng, sau đó nhận thấy một lỗi và cố gắng sửa lỗi nội tuyến thay vì định dạng lại. Tôi tò mò muốn xem liệu đếm ký tự có hiệu quả hơn so với đếm dòng (phương pháp hai lượt thứ 2 so với thứ 3).
Gilles 'SO- ngừng trở nên xấu xa'

3

Một chiến lược hai vượt qua dường như là điều đúng đắn. Thay vì sed tôi sẽ sử dụng awk(1). Hai đường chuyền có thể trông như thế này:

$ LINE=`awk '/^---$/{n=NR}END{print n}' file`

để lấy số dòng. Và sau đó lặp lại tất cả văn bản bắt đầu từ số dòng đó với:

$ awk "NR>$LINE" file

Điều này không cần bộ đệm quá mức.


và sau đó chúng có thể được kết hợp:awk -v line=$(awk '/^---$/{n=NR}END{print n}' file) 'NR>line' file
glenn jackman

Thấy rằng tôi đã có thời gian kiểm tra các bài nộp khác, giờ đây tôi cũng đã kiểm tra đoạn trích "glen jackman" ở trên. Phải mất 0,352 giây (với cùng một tệp dữ liệu được đề cập trong câu trả lời của tôi) ... Tôi bắt đầu nhận được thông báo rằng awk có thể nhanh hơn tôi nghĩ ban đầu có thể (tôi nghĩ rằng sed cũng tốt như nó có, nhưng nó dường như là một trường hợp "ngựa cho các khóa học") ...
Peter.O

Rất thú vị để xem tất cả các kịch bản được điểm chuẩn. Làm tốt lắm Fred.
Mackie Messer

Các giải pháp nhanh nhất sử dụng tactail thực sự đọc tệp đầu vào ngược. Bây giờ, nếu chỉ awk có thể đọc tệp đầu vào ngược ...
Mackie Messer

3
lnum=$(($(sed -n '/^---$/=' file | sed '$!d') +1)); sed -n "${lnum},$ p" file 

Các sedsố dòng đầu tiên của các dòng "---" ...
Số thứ hai sedtrích xuất số cuối cùng từ đầu ra của sed đầu tiên ...
Thêm 1 vào số đó để bắt đầu khối "ccc" của bạn ...
Thứ ba đầu ra 'sed' từ khi bắt đầu khối "ccc" sang EOF

Cập nhật (với các phương pháp Gilles thông tin được tăng cường)

Vâng tôi đã wondereing về cách glenn jackman của tac sẽ thực hiện, vì vậy tôi thời gian thử nghiệm ba câu trả lời (tại thời điểm viết bài) ... Các tập tin thử nghiệm (s) mỗi chứa 1 triệu dòng (số dòng riêng của họ).
Tất cả các câu trả lời đã làm những gì được mong đợi ...

Đây là thời gian ..


Gilles sed (chuyền đơn)

# real    0m0.470s
# user    0m0.448s
# sys     0m0.020s

Gilles awk (chuyền đơn)

# very slow, but my data had a very large data block which awk needed to cache.

Gilles 'hai-pass' (phương pháp đầu tiên)

# real    0m0.048s
# user    0m0.052s
# sys     0m0.008s

Gilles 'hai-pass' (phương pháp thứ hai) ... rất nhanh

# real    0m0.204s
# user    0m0.196s
# sys     0m0.008s

Gilles 'hai-pass' (phương pháp thứ ba)

# real    0m0.774s
# user    0m0.688s
# sys     0m0.012s

Gilles 'gawk' (phương pháp RT) ... rất nhanh , nhưng không phải là POSIX.

# real    0m0.221s
# user    0m0.200s
# sys     0m0.020s

glenn jackman ... rất nhanh , nhưng không phải là POSIX.

# real    0m0.022s
# user    0m0.000s
# sys     0m0.036s

fred.bear

# real    0m0.464s
# user    0m0.432s
# sys     0m0.052s

Mackie Messer

# real    0m0.856s
# user    0m0.832s
# sys     0m0.028s

Vì tò mò, bạn đã thử nghiệm phiên bản hai lượt nào của tôi và bạn đã sử dụng phiên bản awk nào?
Gilles 'SO- đừng trở nên xấu xa'

@Gilles: Tôi đã sử dụng GNU Awk 3.1.6 (trong Ubuntu 10.04 với RAM 4 GB). Tất cả các thử nghiệm có 1 triệu dòng trong "đoạn" đầu tiên, sau đó là "điểm đánh dấu" theo sau là 2 dòng "dữ liệu" ... Phải mất 15,540 giây để xử lý tệp nhỏ hơn 100.000 dòng, nhưng đối với 1.000.000 dòng, tôi chạy nó bây giờ, và nó đã được hơn 25 phút cho đến nay. Nó đang sử dụng một lõi đến 100% ... giết ngay bây giờ ... Dưới đây là một số thử nghiệm gia tăng khác: lines = 100000 (0m16.026s) - lines = 200000 (2m29.990s) - lines = 300000 (5m23. 393s) - dòng = 400000 (11m9.938s)
Peter.O

Rất tiếc .. Trong nhận xét trên của tôi, tôi đã bỏ lỡ tài liệu tham khảo awk "hai vượt qua" của bạn. Chi tiết trên dành cho awk "một lượt" ... Phiên bản awk là chính xác ... Tôi đã nhận xét thêm về các phiên bản "hai lượt" khác nhau dưới câu trả lời của bạn (một kết quả đã được sửa đổi ở trên)
Peter.O


0

Bạn chỉ có thể sử dụng ed

ed -s infile <<\IN
.t.
1,?===?d
$d
,p
q
IN

Cách thức hoạt động: tsao chép dòng ( .) hiện tại - luôn luôn là dòng cuối cùng khi edbắt đầu (chỉ trong trường hợp dấu phân cách có mặt trên dòng cuối cùng), 1,?===?dxóa tất cả các dòng lên đến và bao gồm cả kết quả khớp trước đó ( edvẫn ở dòng cuối cùng ) sau đó $dxóa dòng (trùng lặp) cuối cùng, ,pin bộ đệm văn bản (thay thế bằng wđể chỉnh sửa tệp tại chỗ) và cuối cùng qthoát ed.


Nếu bạn biết có ít nhất một dấu phân cách trong đầu vào (và đừng quan tâm nếu nó cũng được in) thì

sed 'H;/===/h;$!d;x' infile

sẽ ngắn hơn
Cách thức hoạt động: nó nối tất cả các dòng vào Hbộ đệm cũ, nó ghi đè lên hbộ đệm cũ khi gặp một trận đấu, nó dxóa tất cả các dòng ngoại trừ $một dòng khi nó xthay đổi bộ đệm (và tự động in).

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.