Làm thế nào để có được tất cả các dòng giữa lần xuất hiện đầu tiên và cuối cùng của các mẫu?


8

Làm cách nào tôi có thể cắt một tệp (luồng đầu vào tốt) để tôi chỉ nhận được các dòng từ lần xuất hiện đầu tiên của mẫu foođến lần xuất hiện cuối cùng của mẫu bar?

Ví dụ, hãy xem xét các đầu vào sau:

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

Tôi mong đợi đầu ra này:

foo
this 
foo
bar
something
something else
foo
bar

3
Truyền một dòng hoặc một tập tin? Điều này dễ dàng hơn nhiều để làm khi truy cập ngẫu nhiên được cho phép. Với một tập tin, bạn sẽ chỉ cần tìm đầu tiên foovà cuối cùng barvà in mọi thứ ở giữa, nếu có. Với một luồng bạn sẽ phải đọc cho đến đầu tiên foovà đệm tất cả các dòng tiếp theo trong bộ nhớ cho đến EOF, xả bộ đệm mỗi khi barnhìn thấy a. Điều này có thể có nghĩa là đệm toàn bộ luồng trong bộ nhớ.
jw013

Câu trả lời:


6
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'

Các mô hình phù hợp phù hợp /first/,/second/đọc từng dòng một. Khi một số dòng khớp với /first/nó sẽ nhớ nó và mong chờ kết quả khớp đầu tiên cho /second/mẫu. Đồng thời, nó áp dụng tất cả các hoạt động được chỉ định cho mẫu đó. Sau quá trình đó bắt đầu lại nhiều lần cho đến hết tập tin.

Đó không phải là điều chúng ta cần. Chúng ta cần phải tìm đến sự phù hợp cuối cùng của /second/mẫu. Vì vậy, chúng tôi xây dựng xây dựng trông chỉ cho mục đầu tiên /foo/. Khi tìm thấy chu kỳ abắt đầu. Chúng tôi thêm dòng mới vào bộ đệm khớp Nvà kiểm tra xem nó có khớp với mẫu không /bar/. Nếu có, chúng ta chỉ cần in nó và xóa bộ đệm khớp và janyway nhảy đến đầu chu kỳ với ba.

Ngoài ra chúng ta cần xóa biểu tượng dòng mới sau khi dọn dẹp bộ đệm /^\n/s/^\n//. Tôi chắc chắn có giải pháp tốt hơn nhiều, tiếc là nó không xuất hiện trong đầu tôi.

Hy vọng mọi thứ đều rõ ràng.


1
Nó hoạt động! Sẽ thật tuyệt nếu bạn có thể hướng dẫn chúng tôi xây dựng một lệnh như vậy. Tôi cảm thấy ngu ngốc chỉ đơn giản là sao chép / dán nó từ một số trang web trực tuyến;)
rahmu

1
Xin lỗi tôi đã không đăng lời giải thích với câu trả lời. Bây giờ nó là trong bài viết.
vội vàng

Trong một số sedphiên bản như BSD sed (đó là những gì được tìm thấy trên máy Mac), thẻ cần phải được theo sau bởi một dòng mới hoặc kết thúc của chuỗi, do đó tinh chỉnh sau đây là cần thiết: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' Đây cũng hoạt động trên GNU sed, vì vậy tôi nghĩ rằng sửa đổi này (nhiều -eargs kết thúc một arg sau mỗi tên chi nhánh) là một thói quen di động tốt để sử dụng khi sử dụng các nhánh trong sed.
tự đại diện

4

Tôi sẽ làm điều đó với một ít Perl một lót.

cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest
EOF

sản lượng

foo
this 
foo
bar
something
something else
foo
bar

3
Nếu đây là môn đánh gôn, bạn có thể sử dụng Ethay vì e-00777thay vì $/bit (xem perlrun (1)). Mà sẽ rút ngắn nó thành : perl -0777 -nE 'say /(foo.*bar)/s', vẫn có thể đọc được.
Thor

1
Tôi không biết về những lá cờ này! Tôi chắc chắn rằng đặc biệt -0[octal]sẽ tìm thấy nó trong quy trình làm việc của tôi! Cảm ơn vì điều đó
user1146332

3

Đây là một giải pháp sed GNU hai chiều không cần nhiều bộ nhớ:

< infile                                     \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p'                            \
| tr '\n' ' '                                \
| sed 's/ /,/; s/ /p/'                       \
| sed -n -f - infile

Giải trình

  • sedYêu cầu đầu tiên vượt qua vô hạn và tìm thấy sự xuất hiện đầu tiên foovà tất cả các lần xuất hiện tiếp theo của bar.
  • Các địa chỉ này sau đó được định hình thành một sedtập lệnh mới với hai yêu cầu sedvà một tr. Đầu ra của thứ ba sed[start_address],[end_address]p, không có dấu ngoặc.
  • Yêu cầu cuối cùng của việc sedvượt qua infilemột lần nữa, in các địa chỉ được tìm thấy và mọi thứ ở giữa.

2

Nếu tập tin đầu vào vừa vặn thoải mái trong bộ nhớ, hãy giữ nó đơn giản .

Nếu tệp đầu vào là rất lớn, bạn có thể sử dụng csplitđể chia nó thành từng mảnh đầu tiên foovà sau barđó sau đó lắp ráp các mảnh. Các mảnh được gọi piece-000000000, piece-000000001v.v. Chọn một tiền tố (ở đây, piece-) sẽ không xung đột với các tệp hiện có khác.

csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file

(Trên các hệ thống không phải là Linux, bạn sẽ phải sử dụng một số lượng lớn bên trong dấu ngoặc nhọn, ví dụ: {999999999}và vượt qua -ktùy chọn. Số đó là số barphần.)

Bạn có thể lắp ráp tất cả các mảnh với cat piece-*, nhưng điều này sẽ cung cấp cho bạn mọi thứ sau lần đầu tiên foo. Vì vậy, loại bỏ mảnh cuối cùng đầu tiên. Vì tên tệp được tạo bởi csplitkhông chứa bất kỳ ký tự đặc biệt nào, bạn có thể xử lý chúng mà không cần thực hiện bất kỳ biện pháp phòng ngừa trích dẫn đặc biệt nào, ví dụ như với

rm $(echo piece-* | sed 's/.* //')

hoặc tương đương

rm $(ls piece-* | tail -n 1)

Bây giờ bạn có thể tham gia tất cả các phần và loại bỏ các tệp tạm thời:

cat piece-* >output
rm piece-*

Nếu bạn muốn loại bỏ các phần khi chúng được nối để tiết kiệm dung lượng đĩa, hãy thực hiện theo vòng lặp:

mv piece-000000000 output
for x in piece-?????????; do
  cat "$x" >>output; rm "$x"
done

1

Đây là một cách khác với sed:

sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile

Nó nối từng dòng trong /foo/,$phạm vi (các dòng !không nằm trong phạm vi này được dxóa bỏ) vào Hkhông gian cũ. Các dòng không khớp barsẽ bị xóa. Trên các dòng khớp, không gian mẫu được làm trống, e xthay đổi với không gian giữ và dòng trống hàng đầu trong không gian mẫu được loại bỏ.

Với đầu vào lớn và một vài lần xuất hiện, barđiều này sẽ nhanh hơn (nhiều) so với việc kéo từng dòng vào không gian mẫu và sau đó, mỗi lần, kiểm tra không gian mẫu cho bar.
Giải thích:

sed '/foo/,$!d                     # delete line if not in this range
H                                  # append to hold space
/bar/!d                            # if it doesn't match bar, delete 
s/.*//                             # otherwise empty pattern space and
x                                  # exchange hold buffer w. pattern space then
s/\n//                             # remove the leading newline
' infile

Chắc chắn, nếu đây là một tệp (và vừa với bộ nhớ), bạn có thể chỉ cần chạy:

 ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN

bởi vì ed có thể tìm kiếm tiến lùi.
Bạn thậm chí có thể đọc một đầu ra lệnh vào bộ đệm văn bản nếu trình bao của bạn hỗ trợ thay thế quá trình:

printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)

hoặc nếu không, với gnu ed:

printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'

0

Sử dụng bất kỳ awk nào trong bất kỳ hệ vỏ nào trên bất kỳ hệ thống UNIX nào và không đọc toàn bộ tệp hoặc luồng đầu vào vào bộ nhớ cùng một lúc:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar

0

Grep cũng có thể làm điều đó (tốt, GNU grep):

<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'

<infile grep -ozP '        #  call grep to print only the matching section (`-o`)
                           #  use NUL for delimiter (`-z`) (read the whole file).
                           #  And using pcre regex.
(?s)foo.*bar               #  Allow the dot (`.`) to also match newlines.
' | tr '\0' '\n'           #  Restore the NULs to newlines.

Đối với đầu vào từ thân câu hỏi:

$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this 
foo
bar
something
something else
foo
bar
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.