Xóa phạm vi dòng trên mẫu bằng sed (hoặc awk)


28

Tôi có đoạn mã sau sẽ loại bỏ các dòng có mẫu bananavà 2 dòng sau nó:

sed '/banana/I,+2 d' file

Càng xa càng tốt! Nhưng tôi cần loại bỏ 2 dòng trước đó banana , nhưng tôi không thể lấy nó bằng một dấu trừ trừ dấu hiệu hay bất cứ thứ gì (tương tự như những gì grep -v -B2 banana filenên làm nhưng không được):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'

1
Đơn giản nhất là tải tất cả dữ liệu vào một mảng, bỏ qua các dòng không mong muốn sau đó xuất ra những gì còn lại : awk '{l[m=NR]=$0}/banana/{for(i=NR-2;i<=NR;i++)delete l[i]}END{for(i=1;i<=m;i++)if(i in l)print l[i]}'. Điều này không hiệu quả, vì vậy đây chỉ là một gợi ý, không phải là một giải pháp.
thao tác

6
Cứ làm đi tac file | sed ... | tac. : P
angus

@angus Tôi không nghĩ về điều đó;)
Teresa e Junior

1
bạn có thể đã làm sed '/banana/,+2d' file điều đó cũng sẽ làm việc
Akaks

1
Nếu bạn mở sử dụng awk, nó khá đơn giản: awk 'tolower($0)~/bandana/{print prev[!idx];print prev[idx]} {idx=!idx;prev[idx]=$0}' filein Vì đây là nhận xét và không phải là câu trả lời (đã có câu trả lời khác), tôi sẽ không đi sâu vào chi tiết, nhưng mấu chốt của nó là bạn luôn có hai kỷ lục trước đó trong trước [0] và trước [1], là "tươi" tùy thuộc vào sự lặp lại nhưng luôn luôn trong prev[idx], vì vậy khi bạn in, bạn in trong !idxsau đó idxtheo thứ tự. Bất kể, thay thế idxvà đưa hồ sơ hiện tại vào prev[idx].
Luv2code

Câu trả lời:


22

Sed không quay lại: một khi nó đã xử lý một dòng, nó đã hoàn thành. Vì vậy, hãy tìm một dòng và in các dòng N trước đó, sẽ không hoạt động như vậy, không giống như tìm một dòng và in các dòng N tiếp theo mà dễ ghép.

Nếu tệp không quá dài, vì bạn có vẻ ổn với các phần mở rộng GNU, bạn có thể sử dụng tacđể đảo ngược các dòng của tệp.

tac | sed '/banana/I,+2 d' | tac

Một góc tấn công khác là duy trì cửa sổ trượt trong một công cụ như awk. Thích ứng từ Có sự thay thế nào cho các công tắc -A -B -C của grep (để in vài dòng trước và sau) không? (cảnh báo: đã kiểm tra tối thiểu):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

Sử dụng: /path/to/script -v pattern='banana' -v before=2


2
sedcũng có thể làm các cửa sổ trượt, nhưng tập lệnh kết quả thường không thể đọc được để dễ sử dụng hơn awk.
jw013

@Gilles .. awkKịch bản không hoàn toàn đúng; vì nó in các dòng trống và bỏ lỡ các dòng cuối cùng. Điều này dường như khắc phục nó, nhưng nó có thể không lý tưởng hoặc đúng: if (NR-before in h) { print...; delete...; }... và trong ENDphần: for (i in h) print h[i]... Ngoài ra, tập lệnh awk in dòng phù hợp, nhưng tac/secphiên bản thì không; nhưng câu hỏi hơi mơ hồ về điều này .. Kịch bản awk "gốc", mà bạn đã cung cấp một liên kết, hoạt động tốt .. Tôi thích nó ... Tôi không chắc 'mod' ở trên ảnh hưởng đến việc in như thế nào sau dòng ...
Peter.O

@ Peter.O Cảm ơn, kịch bản awk nên tốt hơn bây giờ. Và tôi mất chưa đến 6 tuổi8!
Gilles 'SO- ngừng trở nên xấu xa'

19

Điều này khá dễ dàng với ex hoặc vim -e

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

Biểu thức ghi: đối với mọi dòng chứa chuối trong phạm vi từ dòng hiện tại -2 đến dòng hiện tại, hãy xóa.

Điều thú vị là phạm vi cũng có thể chứa các tìm kiếm ngược và xuôi, ví dụ: điều này sẽ xóa tất cả các phần của tệp bắt đầu bằng một dòng chứa táo và kết thúc bằng một dòng chứa màu cam và chứa một dòng với chuối:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

7

Sử dụng "cửa sổ trượt" trong perl:

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'

6

Bạn có thể làm điều này khá đơn giản với sed:

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

Tôi không biết tại sao mọi người sẽ nói khác, nhưng để tìm một dòng và in các dòng trước đó sed kết hợp với Pnguyên thủy rint tích hợp chỉ viết tối đa \nký tự ewline đầu tiên trong không gian mẫu. Nguyên Dthủy elete bổ sung loại bỏ cùng một phân đoạn không gian mẫu trước khi tái chế đệ quy tập lệnh với những gì còn lại. Và để làm tròn nó, có một nguyên thủy để nối thêm Ndòng đầu vào ext vào không gian mẫu theo sau một \nký tự ewline được chèn .

Vì vậy, một dòng sednên là tất cả bạn cần. Bạn chỉ cần thay thế matchbằng bất cứ thứ gì regrec của bạn và bạn là vàng. Đó nên là một giải pháp rất nhanh là tốt.

Cũng lưu ý rằng nó sẽ tính chính xác một cái khác matchngay trước một cái khác matchvì cả hai đều kích hoạt đầu ra yên tĩnh cho hai dòng trước đó và làm im lặng bản in của nó:


1
7match
8
11match

Để nó hoạt động với số lượng dòng tùy ý , tất cả những gì bạn cần làm là có được một khách hàng tiềm năng.

Vì thế:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

... xóa 5 dòng trước bất kỳ trận đấu nào.


1

Sử dụng man 1 ed:

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
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.