Hapus rentang garis pola di atas dengan sed (atau awk)

28

Saya memiliki kode berikut yang akan menghapus garis dengan pola bananadan 2 baris setelahnya:

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

Sejauh ini baik! Tapi saya perlu menghapus 2 baris sebelumnya banana , tapi saya tidak bisa mendapatkannya dengan "tanda minus" atau apa pun (mirip dengan apa yang grep -v -B2 banana fileharus dilakukan tetapi tidak):

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: `-'
Teresa e Junior
sumber
1
Yang paling mudah adalah untuk memuat semua data ke dalam sebuah array, melewati garis yang tidak diinginkan maka output apa yang tersisa: 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]}'. Ini tidak efisien, jadi ini hanya petunjuk, bukan solusi.
manatwork
6
Lakukan saja tac file | sed ... | tac. : P
angus
@ Kangus, aku tidak memikirkannya;)
Teresa e Junior
1
Anda bisa melakukan sed '/banana/,+2d' file itu juga akan berhasil
Akaks
1
Jika Anda terbuka untuk menggunakan awk, itu cukup sederhana: awk 'tolower($0)~/bandana/{print prev[!idx];print prev[idx]} {idx=!idx;prev[idx]=$0}' filein Karena ini adalah komentar dan bukan jawaban (sudah ada jawaban lain), saya tidak akan membahas terlalu banyak detail, tetapi intinya adalah Anda selalu memiliki dua catatan sebelumnya dalam prev [0] dan prev [1], "segar" tergantung pada iterasi mana tetapi selalu di prev[idx], jadi ketika Anda mencetak, Anda mencetak !idxkemudian idxmemesan. Apapun, gantilah idxdan masukkan catatan saat ini prev[idx].
Luv2code

Jawaban:

22

Sed tidak mundur: setelah diproses garis, selesai. Jadi “temukan garis dan cetak garis N sebelumnya” tidak akan berfungsi sebagaimana mestinya, tidak seperti “temukan garis dan cetak garis N berikutnya” yang mudah dicangkokkan.

Jika file tidak terlalu panjang, karena Anda tampaknya tidak masalah dengan ekstensi GNU, Anda dapat menggunakan tacuntuk membalikkan baris file.

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

Sudut serangan lain adalah mempertahankan jendela geser di alat seperti awk. Beradaptasi dari Adakah alternatif untuk switch grep -A -B -C (untuk mencetak beberapa baris sebelum dan sesudah)? (peringatan: minimal diuji):

#!/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]} }

Pemakaian: /path/to/script -v pattern='banana' -v before=2

Gilles 'SANGAT berhenti menjadi jahat'
sumber
2
seddapat melakukan sliding windows juga, tetapi skrip yang dihasilkan biasanya tidak terbaca sehingga lebih mudah digunakan awk.
jw013
@Gilles .. awkSkripnya kurang tepat; apa adanya mencetak baris kosong dan melewatkan baris terakhir. Ini tampaknya untuk memperbaikinya, tetapi mungkin tidak ideal atau benar sendiri: if (NR-before in h) { print...; delete...; }... dan di ENDbagian: for (i in h) print h[i]... Juga, skrip awk mencetak baris yang cocok, tetapi tac/secversinya tidak; tapi pertanyaannya agak ambigu pada ini .. Script awk "asli", yang Anda berikan tautan, berfungsi dengan baik .. Saya menyukainya ... Saya tidak yakin bagaimana 'mod' di atas mempengaruhi hasil cetak setelah lines ...
Peter.O
@ Peter.O Terima kasih, skrip awk seharusnya lebih baik sekarang. Dan saya membutuhkan waktu kurang dari 6-8 tahun!
Gilles 'SANGAT berhenti menjadi jahat'
19

Ini cukup mudah dengan ex atau vim -e

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

Ekspresi berbunyi: untuk setiap baris yang berisi pisang dalam kisaran dari baris saat ini -2 ke baris saat ini, hapus.

Apa yang keren adalah bahwa rentang juga dapat berisi pencarian maju dan mundur, misalnya ini akan menghapus semua bagian file dimulai dengan baris yang mengandung apel dan diakhiri dengan baris yang mengandung oranye dan berisi baris dengan pisang:

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

Menggunakan "jendela geser" di perl:

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

Anda dapat melakukan ini dengan cukup sederhana dengan sed:

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

Saya tidak tahu mengapa ada orang yang mengatakan sebaliknya, tetapi untuk menemukan garis dan mencetak baris sebelumnya sed menggabungkan built-in Print primitive yang hanya menulis hingga \nkarakter ewline pertama dalam ruang pola. DElete primitif komplementer menghilangkan segmen ruang pola yang sama sebelum secara berulang mendaur ulang skrip dengan yang tersisa. Dan untuk mengatasinya, ada primitif untuk menambahkan jalur Ninput ext ke ruang pola mengikuti \nkarakter ewline yang dimasukkan .

Sehingga satu baris sedharus menjadi semua yang Anda butuhkan. Anda tinggal mengganti matchdengan regexp apa pun Anda dan Anda emas. Itu harus menjadi solusi yang sangat cepat juga.

Perhatikan juga bahwa ia akan menghitung dengan benar yang matchmendahului yang lain matchsebagai pemicu untuk menghentikan output untuk dua baris sebelumnya dan juga menghentikan pencetakannya:


1
7match
8
11match

Agar dapat bekerja untuk jumlah baris yang sewenang - wenang , yang perlu Anda lakukan adalah mendapatkan petunjuk.

Begitu:

    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

... menghapus 5 baris sebelum pertandingan apa pun.

mikeserv
sumber
1

Menggunakan 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
larz
sumber