Grep-व्युत्क्रम-मिलान कैसे करें और "पहले" और "बाद" लाइनों को बाहर करें


26

निम्नलिखित प्रविष्टियों के साथ एक पाठ फ़ाइल पर विचार करें:

aaa
bbb
ccc
ddd
eee
fff
ggg
hhh
iii

एक पैटर्न (जैसे fff) को देखते हुए , मैं आउटपुट में प्राप्त करने के लिए ऊपर की फ़ाइल को संक्षिप्त करना चाहूंगा:

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

उदाहरण के लिए, यदि B = 2और A = 1, पैटर्न के साथ आउटपुट = fffहोना चाहिए:

aaa
bbb
ccc
hhh
iii

मैं इसे grep या अन्य कमांड लाइन टूल के साथ कैसे कर सकता हूं?


ध्यान दें, जब मैं कोशिश करता हूं:

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

मुझे वह नहीं मिलता जो मैं चाहता हूं। मुझे इसके बजाय:

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

जवाबों:


9

ज्यादातर मामलों में डॉन बेहतर हो सकता है, लेकिन सिर्फ इस मामले में कि फाइल वास्तव में बड़ी है, और आप sedएक स्क्रिप्ट फाइल को संभाल नहीं सकते हैं जो बड़ी (जो कि स्क्रिप्ट की लगभग 5000+ पंक्तियों पर हो सकती है) , यहाँ यह सादे के साथ है 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'

यह एक उदाहरण है जिसे इनपुट पर स्लाइडिंग विंडो कहा जाता है । यह कुछ भी मुद्रित करने का प्रयास करने से पहले -count लाइनों के लुक-$B फॉरवर्ड बफर का निर्माण करके काम करता है ।

और वास्तव में, शायद मुझे अपने पिछले बिंदु को स्पष्ट करना चाहिए: इस समाधान और डॉन दोनों के लिए प्राथमिक प्रदर्शन सीमांत सीधे अंतराल से संबंधित होगा। यह समाधान बड़े अंतराल के आकार के साथ धीमा होगा , जबकि बड़े अंतराल आवृत्तियों के साथ डॉन की गति धीमी होगी । दूसरे शब्दों में, भले ही इनपुट फ़ाइल बहुत बड़ी है, अगर वास्तविक अंतराल घटना अभी भी बहुत ही अनियंत्रित है, तो उसका समाधान संभवतः जाने का रास्ता है। हालांकि, यदि अंतराल आकार अपेक्षाकृत प्रबंधनीय है, और अक्सर होने की संभावना है, तो यह वह समाधान है जिसे आपको चुनना चाहिए।

तो यहाँ वर्कफ़्लो है:

  • यदि $matchएक \nईवलाइन से पहले के पैटर्न स्पेस में पाया जाता है , तो पूर्ववर्ती हर ईवलाइन sedको पुन: प्राप्त कर लेगा जो इसे पसंद करता है। D\n
    • मैं $matchपहले से पूरी तरह से पैटर्न की जगह को साफ कर रहा था - लेकिन आसानी से ओवरलैप को संभालने के लिए, एक लैंडमार्क को छोड़ना कहीं बेहतर काम लगता है।
    • मैंने s/.*\n.*\($match\)/\1/इसे एक बार में प्राप्त करने का प्रयास किया और लूप को चकमा दिया, लेकिन जब $A/$Bबड़े होते हैं, तो Dइलेट लूप काफी तेज साबित होता है।
  • फिर हम Nएक \newline सीमांकक से पहले इनपुट की एक्सट्रीम लाइन में खींचते हैं और अपने सबसे हाल ही में उपयोग किए गए रेगुलर एक्सप्रेशन w / का हवाला देकर Dएक /\n.*$match/बार फिर से ई-मेल करने की कोशिश करते हैं //
  • यदि पैटर्न स्पेस मेल करता है $matchतो यह केवल $matchपंक्ति के प्रमुख के साथ ही हो सकता है - सभी $Before पंक्तियों को साफ़ कर दिया गया है।
    • तो हम $After पर लूपिंग शुरू करते हैं ।
    • इस लूप के प्रत्येक रन हम करने का प्रयास करेंगे s///ubstitute के लिए &खुद को $Aवें \nपैटर्न अंतरिक्ष में ewline चरित्र, और, यदि सफल, tहमारे पूरे और - स्था हमें शाखा जाएगा $After बफर - बाहर स्क्रिप्ट की पूरी तरह से ऊपर से अधिक स्क्रिप्ट शुरू करने के लिए अगली इनपुट लाइन के साथ यदि कोई हो।
    • अगर tएस्ट सफल नहीं है, तो हम ऑप लेबल पर bवापस :tआएँगे और इनपुट की एक और लाइन के लिए फिर से जुट जाएँगे - संभवतया लूप को शुरू करने पर अगर फेरी $matchइकट्ठा करते समय होती है $A
  • अगर हम अतीत एक मिल $matchसमारोह पाश, तो हम करने की कोशिश करेंगे pप्रिंट करें $अंतिम पंक्ति है कि अगर यह यह है, और अगर !की कोशिश मत s///के लिए ubstitute &खुद $Bवें \nपैटर्न अंतरिक्ष में ewline चरित्र।
    • हम इसे भी tइस्ट करेंगे , और अगर यह सफल होता है तो हम :Pरिंट लेबल पर शाखा देंगे ।
    • यदि हम :top करने के लिए वापस शाखा नहीं करेंगे और बफर में संलग्न एक और इनपुट लाइन प्राप्त करेंगे ।
  • अगर हम यह करने के लिए :Pप्रिंट करें हम करेंगे Pतो प्रिंट करें Dपहले अप करने के लिए हटाएं \nपैटर्न अंतरिक्ष में ewline और क्या रहता है के साथ ऊपर से स्क्रिप्ट को फिर से चलाएं।

और इसलिए इस बार, अगर हम कर रहे थे A=2 B=2 match=5; seq 5 | sed...

:Pरिंट पर पहले पुनरावृत्ति के लिए पैटर्न स्थान इस तरह दिखेगा:

^1\n2\n3$

और इसी तरह से sedइसके $Before बफर को इकट्ठा करता है । और इसलिए जो इनपुट इकट्ठा हुआ है उसके पीछेsed आउटपुट $Bलाइनों को प्रिंट करता है। इसका मतलब यह है कि, हमारे पिछले उदाहरण को देखते हुए, आउटपुट के लिए रिंट होगा , और फिर उस एलीट और स्क्रिप्ट के शीर्ष पर वापस भेजें जो एक पैटर्न स्पेस की तरह दिखता है:sedP1D

^2\n3$

... और स्क्रिप्ट के शीर्ष पर Nएक्सट्रीम इनपुट लाइन को पुनः प्राप्त किया जाता है और इसलिए अगला पुनरावृति जैसा दिखता है:

^2\n3\n4$

और इसलिए जब हम 5इनपुट की पहली घटना पाते हैं , तो पैटर्न स्पेस वास्तव में जैसा दिखता है:

^3\n4\n5$

फिर Dईट लूप में किक करता है और जब इसके माध्यम से ऐसा दिखता है:

^5$

और जब Nएक्सट्रीम इनपुट लाइन खींची जाती है तो sedईओएफ और क्विट्स हिट होते हैं। उस समय तक यह केवल P1 और 2 की पंक्तियों का संकेत देता है।

यहाँ एक उदाहरण चलाया गया है:

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'

वह प्रिंट:

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

मैं वास्तव में बड़ी फ़ाइलों के साथ काम कर रहा हूं, और डॉन का जवाब इस समाधान की तुलना में काफी धीमा था। मैं शुरू में अपना स्वीकृत उत्तर बदलने में संकोच कर रहा था, लेकिन गति का अंतर काफी दिखाई दे रहा है।
अमेलियो वाज़केज़-रीना

4
@ एमेलियो - यह किसी भी आकार की एक धारा के साथ काम करेगा, और इसे काम करने के लिए फ़ाइल को पढ़ने की आवश्यकता नहीं है। सबसे बड़ा प्रदर्शन कारक $A/ और / या का आकार है $B। आप जितनी बड़ी संख्याएँ बनाते हैं, यह उतनी ही धीमी हो जाएगी - लेकिन आप उन्हें यथोचित रूप से बड़ा बना सकते हैं।
mikeserv

1
@ AmelioVazquez-Reina - यदि आप पुराने का उपयोग कर रहे हैं, तो यह बेहतर है, मुझे लगता है।
मोकेसर

11

आप जिस फ़ाइल को छोड़ना चाहते हैं उसके भागों के gnu grepसाथ प्रयोग कर सकते हैं -Aऔर -Bप्रिंट कर सकते हैं लेकिन -nलाइन नंबरों को प्रिंट करने के लिए स्विच को भी जोड़ सकते हैं और फिर आउटपुट को प्रारूपित कर सकते हैं और इसे sedउन पंक्तियों को हटाने के लिए कमांड स्क्रिप्ट के रूप में पास कर सकते हैं:

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

यह भी उदाहरण के लिए, के grepमाध्यम से पारित पैटर्न की फ़ाइलों के साथ काम करना चाहिए -f:

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

मुझे लगता है कि इसे थोड़ा अनुकूलित किया जा सकता है अगर यह किसी भी तीन या अधिक लगातार लाइन नंबरों को सीमा में ढकेल देता है, जैसे कि 2,6dइसके बजाय जैसे 2d;3d;4d;5d;6d... हालांकि अगर इनपुट में केवल कुछ मैच हैं तो यह करने लायक नहीं है।


अन्य तरीके कि लाइन क्रम बनाए रखने के नहीं है और सबसे अधिक संभावना कम होती है:
के साथ comm:

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

commसॉर्ट किए गए इनपुट की आवश्यकता होती है, जिसका अर्थ है कि लाइन ऑर्डर अंतिम आउटपुट में संरक्षित नहीं किया जाएगा (जब तक कि आपकी फ़ाइल पहले से ही सॉर्ट नहीं हो जाती है) इसलिए सॉर्ट nlकरने से पहले लाइनों को नंबर करने के लिए उपयोग किया जाता है, comm -13केवल 2 FILE के लिए अद्वितीय लाइनों को प्रिंट करता है और फिर cutउस भाग को हटा देता है जिसे जोड़ा गया था nl(जो है, पहले क्षेत्र और सीमांकक :)
के साथ join:

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

धन्यवाद डॉन! त्वरित प्रश्न, क्या आप के commसाथ sedऔर मूल के साथ तेजी से समाधान की उम्मीद करेंगे grep?
एमेलियो वाज़केज़-रीना

1
@ AmelioVazquez-Reina - मुझे नहीं लगता कि यह अभी भी इनपुट फ़ाइल को दो बार पढ़ता है (साथ ही यह कुछ छंटाई करता है) माइक के समाधान के विपरीत है जो केवल एक बार फाइल को संसाधित करता है।
डॉन_क्रिस्टी

9

यदि आप का उपयोग करने में कोई आपत्ति नहीं है vim:

$ export PAT=fff A=1 B=2
$ vim -Nes "+g/${PAT}/.-${B},.+${A}d" '+w !tee' '+q!' foo
aaa
bbb
ccc
hhh
iii
  • -Nesगैर-संगत, मौन पूर्व मोड चालू करता है। पटकथा के लिए उपयोगी है।
  • +{command}{command}फ़ाइल पर चलाने के लिए विम को बताएं ।
  • g/${PAT}/- सभी लाइनों के मिलान पर /fff/। यह मुश्किल हो जाता है अगर पैटर्न में नियमित अभिव्यक्ति विशेष वर्ण होते हैं जो आपने उस तरीके से इलाज करने का इरादा नहीं किया था।
  • .-${B} - इस एक के ऊपर 1 लाइन से
  • .+${A}- इस एक के नीचे 2 रेखाएँ ( :he cmdline-rangesइन दोनों के लिए देखें )
  • d - लाइनों को हटा दें।
  • +w !tee फिर मानक उत्पादन के लिए लिखता है।
  • +q! परिवर्तन को सहेजे बिना समाप्त हो जाता है।

आप चर को छोड़ सकते हैं और सीधे पैटर्न और संख्याओं का उपयोग कर सकते हैं। मैंने उन्हें सिर्फ उद्देश्य की स्पष्टता के लिए इस्तेमाल किया।


3

कैसे (GNU का उपयोग करके ) grepऔर bash:

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

यहां हम रेखाओं को त्यागने के लिए खोज रहे हैं grep -B2 -A1 'fff' file.txt, फिर इसे इनपुट फ़ाइल के रूप में उपयोग करके वांछित रेखाओं का पता लगा सकते हैं।


हम्म, यह मेरी मशीन (ओएस एक्स) पर कुछ भी आउटपुट नहीं करता है
एमिलियो वाज़केज़-रीना

@ AmelioVazquez-Reina के बारे में खेद है कि .. मुझे आपके OS से पहले नहीं पता था..क्योंकि मैंने उबंटू पर यह परीक्षण किया है ..
heemayl

2
यह एक ही समस्या है kos(अब हटाए गए) समाधान के रूप में अगर इनपुट फ़ाइल में डुप्लिकेट लाइनें हैं और उनमें से कुछ सीमा के बाहर हैं और अन्य उस सीमा के अंदर हैं तो यह उन सभी को हटा देगा। इसके अलावा, पैटर्न की कई घटनाओं के साथ , यदि --इनपुट फ़ाइल (श्रेणियों के बाहर) जैसी लाइनें हैं, तो यह उन्हें हटा देगा क्योंकि सीमांकक आउटपुट --में प्रकट होता है grepजब एक से अधिक रेखाएं पैटर्न से मेल खाती हैं (बाद वाली अत्यधिक संभावना नहीं है, लेकिन लायक है उल्लेख) मुझे लगता है।
don_crissti

@don_crissti धन्यवाद..आप सही हैं..हालांकि मैं ओपी का उदाहरण सचमुच ले रहा था..मैं इसे छोड़ने जा रहा हूं अगर कोई इसे बाद में मददगार मिले ..
heemayl

1

आप अस्थायी फ़ाइलों का उपयोग करके एक अच्छे-पर्याप्त परिणाम तक पहुँच सकते हैं:

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

परिणाम अच्छा-पर्याप्त है क्योंकि आप प्रक्रिया में कुछ इंडेंटेशन को ढीला कर सकते हैं, लेकिन अगर यह एक xml या इंडेंटेशन असंवेदनशील फ़ाइल है तो यह समस्या नहीं होनी चाहिए। चूंकि यह स्क्रिप्ट रैम ड्राइव का उपयोग करती है, इसलिए उन टेंप फाइलों को लिखना और पढ़ना स्मृति में काम करने में उतना ही तेज है।


1

इसके अलावा, यदि आप किसी दिए गए मार्कर के आगे कुछ लाइनों को छोड़ना चाहते हैं, तो आप उपयोग कर सकते हैं:

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

( /programming//a/1492538 पर ग्लेन जैकमैन )

कुछ आदेशों को पाइप करके आप व्यवहार के पहले / बाद में प्राप्त कर सकते हैं:

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
शानदार, awkजब आप लाइनों को प्रभावित करने से पहले और परिणाम को फिर से उलटने का मतलब है, तो लाइनों को संभालने के लिए एक उलट फ़ाइल पर उपयोग करें ।
कर्मकाज़े

0

इसे पूरा करने का एक तरीका, हो सकता है कि चर बनाने के लिए सबसे आसान तरीका निम्नलिखित हो:

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

इस तरह से आपके पास अभी भी अपनी संरचना है। और आप आसानी से एक लाइनर से देख सकते हैं कि आप क्या हटाने की कोशिश कर रहे हैं।

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

हेमेयेल के समान समाधान, और don_crissti द्वारा वर्णित के रूप में एक ही समस्या: यह कोस के (अब हटाए गए) समाधान के समान समस्या होगी जैसे कि इनपुट फ़ाइल में डुप्लिकेट लाइनें हैं और उनमें से कुछ सीमा के बाहर हैं और अन्य उस सीमा के अंदर हैं। यह उन सभी को हटा देगा। इसके अलावा, पैटर्न की कई घटनाओं के साथ, अगर वहाँ लाइनें हैं - इनपुट फ़ाइल में (पर्वतमाला के बाहर) यह उन्हें हटा देगा क्योंकि सीमांकक - grep के आउटपुट में प्रकट होता है जब एक से अधिक लाइन पैटर्न से मेल खाती है (उत्तरार्द्ध अत्यधिक है) संभावना नहीं है, लेकिन उल्लेख के लायक है)।
बोडो थेसेन

0

यदि केवल 1 मैच हो:

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

अन्यथा (जाग):

# -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)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.