“Önce” ve “sonra” satırlarını grep-ters-eşleştirme ve hariç tutma


26

Aşağıdaki girişleri içeren bir metin dosyası düşünün:

aaa
bbb
ccc
ddd
eee
fff
ggg
hhh
iii

Bir desen verildiğinde (örn. fff) Çıktıyı almak için yukarıdaki dosyayı grep etmek istiyorum:

all_lines except (pattern_matching_lines  U (B lines_before) U (A lines_after))

Örneğin, eğer B = 2ve A = 1, pattern = ile çıktı şöyle fffolmalıdır:

aaa
bbb
ccc
hhh
iii

Grep veya diğer komut satırı araçlarıyla bunu nasıl yapabilirim?


Dikkat et, denediğimde:

grep -v 'fff'  -A1 -B2 file.txt

Ne istediğimi anlamadım. Bunun yerine alıyorum:

aaa
bbb
ccc
ddd
eee
fff
--
--
fff
ggg
hhh
iii

Yanıtlar:


9

don, çoğu durumda daha iyi olabilir, ancak dosyanın gerçekten büyük olması ve büyük sedbir komut dosyası (5000'in üzerinde komut satırında olabilir) ile baş edememeniz durumunda , işte düz sed:

sed -ne:t -e"/\n.*$match/D" \
    -e'$!N;//D;/'"$match/{" \
            -e"s/\n/&/$A;t" \
            -e'$q;bt' -e\}  \
    -e's/\n/&/'"$B;tP"      \
    -e'$!bt' -e:P  -e'P;D'

Bu girişte sürgülü pencere denilen bir örnek . Bu bir inşa ederek çalışır ileriye bakma ait tampon $Bhiç bir şey yazdırmak için çalışmadan önce -count hatları.

Ve aslında, muhtemelen daha önceki noktamı açıklığa kavuşturmalıyım: Hem bu çözüm hem de Don’lar için birincil performans sınırlayıcısı doğrudan aralıklarla ilgili olacak. Bu çözelti, daha büyük bir aralık ile yavaş olacak boyutlarda don en büyük aralığı ile yavaş olacak ise, frekans . Başka bir deyişle, giriş dosyası çok büyük olsa bile, gerçek aralık oluşumu hala çok nadirse, o zaman çözümü muhtemelen yoludur. Ancak, aralık büyüklüğü nispeten yönetilebilir ve sık sık meydana gelmesi muhtemelse, seçmeniz gereken çözüm budur.

İşte iş akışı:

  • Eğer $matchbir öncesinde desen uzayda bulunan \newline, sedyinelemeli olacak Dher elete \newline olduğunu ilerlettiği o.
    • $match'Nin kalıp alanını daha önce tamamen temizledim - fakat çakışmayı kolayca idare etmek için bir dönüm noktası bırakmak çok daha iyi çalışıyor gibi görünüyor.
    • Aynı zamanda s/.*\n.*\($match\)/\1/bir seferde onu almaya çalışarak döngüyü atlatmaya çalıştım , ancak $A/$Bbüyük olduğunda Dseçkin döngü önemli ölçüde daha hızlı oluyor.
  • Sonra, ewline sınırlayıcısının Nönündeki girişin ext satırını \nçekeriz ve en son kullanılan w / w düzenli ifademize başvurarak Dbir /\n.*$match/kez daha seçmeye çalışırız //.
  • Desen alanı eşleşirse $match, bunu yalnızca $matchçizginin başında yapabilir - tüm $Before çizgiler temizlenir.
    • Böylece daha $Asonradan döngü başlıyoruz .
    • Bu döngünün her koşmak biz deneriz s///için yerini tutmaz &kendisi $Ainci \ndesen uzayda ewline karakterini ve başarılı olursa, tbizim bütün ve - est bizi dala ayrılacak $Atamamen üstten üzerinde senaryoyu başlatmak için komut dosyası dışarı - fter tamponunu eğer varsa bir sonraki giriş hattında.
    • Eğer test başarılı olmazsa b, :top etiketine geri döneceğiz ve başka bir girdi satırı için tekrar elde edeceğiz - muhtemelen daha sonra $matchtoplama yapılırsa döngü $Abaştan başlar.
  • Biz geçmiş olsun $matchfonksiyon döngü, o zaman çalışacağım pRint $eğer bu bunu ise son satırı ve !etmeye s///yönelik yerini tutmaz &kendisi $Binci \ndesen uzayda ewline karakteri.
    • Bunu da ttahmin edeceğiz ve eğer başarılı olursa :Print etiketine geçeceğiz .
    • Olmazsa op'a geri dönelim :tve arabelleğe başka bir giriş satırı ekleyelim.
  • Biz yaparsak o kadar :Pbiz edeceğiz Rint Psonra Rint Dilk kadar elete \ndesen uzayda ewline ve kalanları ile üstten senaryoyu yeniden çalıştırın.

Ve bu sefer, eğer yapıyor olsaydık A=2 B=2 match=5; seq 5 | sed...

:PRint'deki ilk yineleme için kalıp alanı şöyle görünür:

^1\n2\n3$

Ve bu sed, $Before arabelleğini böyle toplar . Ve böylece topladığı girdinin arkasındakised çıktı- $Bsayı satırlarına yazdırır . Daha önceki örnekte verilen bu araçlar, olur rint çıkışına ve sonra elete o ve senaryonun üstüne gibi hangi görünüyor desen alanını geri göndermek:sedP1D

^2\n3$

... ve betiğin üstünde Next giriş satırı alınır ve bir sonraki yineleme şöyle görünür:

^2\n3\n4$

Ve 5girişin ilk oluşumunu bulduğumuzda , desen alanı aslında şöyle görünür:

^3\n4\n5$

Sonra Dseçkin döngü devreye girer ve içinden geçtiğinde şöyle görünür:

^5$

Ve ne zaman Next giriş hattı çekilir sedhit EOF ve sonlandırılıyor. O zamana kadar sadece P1. ve 2. satırları değiştirdi.

İşte bir örnek çalışma:

A=8 B=7 match='[24689]0'
seq 100 |
sed -ne:t -e"/\n.*$match/D" \
    -e'$!N;//D;/'"$match/{" \
            -e"s/\n/&/$A;t" \
            -e'$q;bt' -e\}  \
    -e's/\n/&/'"$B;tP"      \
    -e'$!bt' -e:P  -e'P;D'

Bu yazdırır:

1
2
3
4
5
6
7
8
9
10
11
12
29
30
31
32
49
50
51
52
69
70
71
72
99
100

Aslında büyük dosyalarla çalışıyorum ve Don'un cevabı bu çözümden belirgin şekilde daha yavaştı. Başlangıçta kabul ettiğim cevabı değiştirmek konusunda tereddüt ettim, ancak hız farkı oldukça belirgin.
Amelio Vazquez-Reina

4
@Amelio - bu herhangi bir boyuttaki akışla çalışacaktır ve çalışması için dosyayı okumasına gerek yoktur. En büyük performans faktörü $Ave / veya büyüklüğüdür $B. Bu sayıları ne kadar büyük yaparsanız, o kadar yavaş olur - ancak bunları oldukça büyük yapabilirsiniz.
mikeserv

1
@ AmelioVazquez-Reina - eğer eskisini kullanıyorsanız, bu daha iyi, sanırım.
mikeserv

11

Sen kullanabilirsiniz gnu grepile -Ave -Bdışlamak ama eklemek istediğiniz dosyanın tam olarak parçalar yazdırmak için -nayrıca hat numaralarını yazdırmak ve ardından çıktıyı biçimlendirmek ve bir komut dosyası olarak geçmek için anahtarı sedbu satırları silmek için:

grep -n -A1 -B2 PATTERN infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile

Bu aynı zamanda geçirilen desen dosyalarla çalışması gerekir greparacılığıyla -förneğin:

grep -n -A1 -B2 -f patterns infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile

Girdide birkaç eşleşmeye sahip olsa bile, 2,6dbunun yerine, örneğin 2d;3d;4d;5d;6d... yerine, üç veya daha fazla ardışık satır numarasını aralıklarla daraltırsa, bunun biraz daha optimize edilebileceğini düşünüyorum .


: Satır düzeni korumak ve büyük olasılıkla daha yavaş yok diğer yolları
ile comm:

comm -13 <(grep PATTERN -A1 -B2 <(nl -ba -nrz -s: infile) | sort) \
<(nl -ba -nrz -s: infile | sort) | cut -d: -f2-

commsıralı giriş gerektirir, yani satır sırasının son çıktıda korunmayacağı anlamına gelir (dosyanız zaten sıralanmamışsa) nl, sıralamadan önce satırları numaralandırmak için kullanılır, comm -13yalnızca 2. DOSYA'ya özgü satırları yazdırır ve ardından cuttarafından eklenen kısmı kaldırır. nl(yani, ilk alan ve sınırlayıcı :)
şununla join:

join -t: -j1 -v1 <(nl -ba -nrz -s:  infile | sort) \
<(grep PATTERN -A1 -B2 <(nl -ba -nrz -s:  infile) | sort) | cut -d: -f2-

Don teşekkürler! Hızlı soru, sen ile çözüm beklenebilir commorijinal olandan daha hızlı olması için sedve grep?
Amelio Vazquez-Reina

1
@ AmelioVazquez-Reina - Dosyayı sadece bir kez işleyen Mike'ın çözümünün aksine hala girdi dosyasını iki kez okuduğunu (bazı sıralamaları yazdığını) sanmıyorum .
don_crissti

9

Sakıncası yoksa vim:

$ export PAT=fff A=1 B=2
$ vim -Nes "+g/${PAT}/.-${B},.+${A}d" '+w !tee' '+q!' foo
aaa
bbb
ccc
hhh
iii
  • -Nesuyumlu olmayan sessiz modunu açar. Komut dosyası için kullanışlıdır.
  • +{command}vim'e {command}dosya üzerinde çalışmasını söyle .
  • g/${PAT}/- eşleşen tüm hatlarda /fff/. Desen, bu şekilde ele alma niyetinde olmadığınız özel ifadeler içeren normal karakterler içeriyorsa, bu zorlaşır.
  • .-${B} - bunun üstünde 1 satırdan itibaren
  • .+${A}- bunun altındaki 2 satıra kadar ( :he cmdline-rangesbu ikisi için bakınız )
  • d - Çizgileri sil.
  • +w !tee daha sonra standart çıktıya yazar.
  • +q! değişiklikleri kaydetmeden çıkar.

Değişkenleri atlayabilir ve desen ve sayıları doğrudan kullanabilirsiniz. Onları sadece amaç netliği için kullandım.


3

Peki ya (GNU grepve kullanarak bash):

$ grep -vFf - file.txt < <(grep -B2 -A1 'fff' file.txt)
aaa
bbb
ccc
hhh
iii

Burada atılacak satırları buluyoruz grep -B2 -A1 'fff' file.txt, sonra bunları atmak istenen satırları bulmak için bir giriş dosyası olarak kullanıyoruz.


Hmm, bu benim
makineme

@ AmelioVazquez-Reina bunun için üzgünüm .. ben daha önce işletim sisteminizi bilmiyordum .. ben yine de Ubuntu'da test
ettim

2
Bu kos, giriş dosyasında yinelenen satırlar varsa ve bazıları aralığın dışına düştüğünde ve bazıları da bu aralığın içinde kaldıklarında, bunların tümünü silecek gibi (şimdi silinmiş) çözümü ile aynı soruna sahip olacaktır. Ayrıca, çoklu desen oluşumlarında --, giriş dosyasındaki gibi çizgiler varsa (aralıkların dışında) bu onları silecektir çünkü sınırlayıcı çıktısında bir satırdan daha fazla eşleşme deseni-- göründüğünde (ikincisi çok düşüktür ancak değer değerindedir) bahsetti sanırım). grep
don_crissti

@ don_crissti Teşekkürler ... haklısın ... OP'in örneğini tam anlamıyla alıyor olmama rağmen ... birisi daha sonra yararlı bulabilirse bırakacağım ..
heemayl

1

Geçici dosyaları kullanarak yeterince iyi bir sonuca ulaşabilirsiniz:

my_file=file.txt #or =$1 if in a script

#create a file with all the lines to discard, numbered
grep -n -B1 -A5 TBD "$my_file" |cut -d\  -f1|tr -d ':-'|sort > /tmp/___"$my_file"_unpair

#number all the lines
nl -nln "$my_file"|cut -d\  -f1|tr -d ':-'|sort >  /tmp/___"$my_file"_all

#join the two, creating a file with the numbers of all the lines to keep
#i.e. of those _not_ found in the "unpair" file
join -v2  /tmp/___"$my_file"_unpair /tmp/___"$my_file"_all|sort -n > /tmp/___"$my_file"_lines_to_keep

#eventually use these line numbers to extract lines from the original file
nl -nln $my_file|join - /tmp/___"$my_file"_lines_to_keep |cut -d\  -f2- > "$my_file"_clean

Sonuç yeterlidir çünkü işlem sırasında bir miktar girinti kaybedebilirsiniz, ancak eğer bir xml veya girinti duyarsız bir dosya ise bu bir problem olmamalıdır. Bu komut dosyası bir ram sürücüsü kullandığından, bu geçici dosyaları yazmak ve okumak bellekte çalışmak kadar hızlıdır.


1

Ayrıca, belirli bir işaretçinin önüne bazı satırları hariç tutmak istiyorsanız, aşağıdakileri kullanabilirsiniz:

awk -v nlines=2 '/Exception/ {for (i=0; i<nlines; i++) {getline}; next} 1'

( https://stackoverflow.com/a/1492538 adresindeki glenn jackman )

Bazı komutları kullanarak, davranış öncesi / sonrası alabilirsiniz:

awk -v nlines_after=5 '/EXCEPTION/ {for (i=0; i<nlines_after; i++) {getline};print "EXCEPTION" ;next} 1' filename.txt|\
tac|\
awk -v nlines_before=1 '/EXCEPTION/ {for (i=0; i<nlines_before; i++) {getline}; next} 1'|\
tac

1
Daha awkönce satırları etkilemek ve sonucu yeniden tersine çevirmek istediğinizde, aşağıdaki satırları işlemek için mükemmel bir dosya kullanın.
karmakaze

0

Bunu başarmanın bir yolu, belki de en kolay yol bir değişken oluşturmak ve aşağıdakileri yapmak olacaktır:

grep -v "$(grep "fff" -A1 -B2 file.txt)" file.txt

Bu şekilde hala yapınız var. Ve bir astardan kolayca çıkarmaya çalıştığınız şeyi görebilirsiniz.

$ grep -v "$(grep "fff" -A1 -B2 file.txt)" file.txt
aaa
bbb
ccc
hhh
iii

heemayl ile aynı çözüm ve don_crissti tarafından açıklananla aynı sorun: Bu, giriş dosyasında yinelenen satırlar varsa ve bazıları aralığın dışına düştüğünde ve bazıları aralığın içinde kaldıklarında, kos'un (şimdi silinmiş) çözümüyle aynı soruna sahip olur. bu hepsini siler. Ayrıca, çoklu desen oluşumlarında, eğer girdi dosyasında (aralıkların dışında) gibi satırlar varsa bu onları silecektir çünkü sınırlayıcı - grep'in çıktısında bir satırdan daha fazla eşleşirse görünür (ikincisi oldukça yüksektir). olası değil ama bahsetmeye değer sanırım).
Bodo Thiesen

0

Yalnızca 1 eşleşme varsa:

A=1; B=2; n=$(grep -n 'fff' file.txt | cut -d: -f1)
head -n $((n-B-1)) file.txt ; tail -n +$((n+A+1)) file.txt

Aksi takdirde (awk):

# -vA=a -vB=b -vpattern=pat must be provided
BEGIN{

    # add file again. assume single file
    ARGV[ARGC]=ARGV[ARGC-1]
    ++ARGC
}

# the same as grep -An -Bn pattern
FNR==NR && $0 ~ pattern{
    for (i = 0; i <= B; ++i)
        a[NR-i]++
    for (i = 1; i <= A; ++i)
        a[NR+i]++
}

FNR!=NR && !(FNR in a)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.