कैसे एक regex द्वारा एक फ़ाइल से कई लाइनें प्राप्त करने के लिए?


10

कैसे एक regex द्वारा एक फ़ाइल से कई लाइनें प्राप्त करने के लिए?

मैं अक्सर एक regex द्वारा कई लाइनों / कई लाइनों को संशोधित करना चाहते हैं। एक उदाहरण मामला:

मैं एक XML / SGML फ़ाइल का हिस्सा पढ़ने की कोशिश कर रहा हूं (वे जरूरी अच्छी तरह से या पूर्वानुमानित वाक्यविन्यास में नहीं हैं, इसलिए एक regex एक उचित पार्सर की तुलना में सुरक्षित होगा। इसके अलावा मैं ऐसा करने में सक्षम होना चाहूंगा जो पूरी तरह से सफेद भी हो। असंरचित फाइलें जहां केवल कुछ प्रमुख शब्दों को जाना जाता है।) एक शेल स्क्रिप्ट में (सोलारिस और लिनक्स पर चल रहा है)।

उदाहरण XML:

<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>

इससे मैं यह पढ़ना चाहूंगा कि <tag1>अगर इसमें fooकहीं यह शामिल है।

एक रेगेक्स की तरह (<tag1>.*?foo.*?</tag1>)सही भाग देना चाहिए, लेकिन उपकरण जैसे grepऔर sedकेवल मेरे लिए एकल लाइनों के साथ काम करते हैं। मुझे कैसे मिल सकता हैं

<tag1>
 <tag2>foo</tag2>
</tag1>

इस उदाहरण में



@evilsoup यह सच है, लेकिन मेरा सवाल विशेष रूप से XML / SGML फ़ाइलों के बारे में नहीं है, बस किसी भी पाठ फ़ाइलों के बारे में।
डेन

जवाबों:


7

यदि आपके पास GNU grep स्थापित है, तो आप -P(perl-regex) ध्वज के PCRE_DOTALLसाथ गुजर कर बहु-खोज खोज कर सकते हैं और साथ सक्रिय कर सकते हैं(?s)

grep -oP '(?s)<tag1>(?:(?!tag1).)*?foo(?:(?!tag1).)*?</tag1>' file.txt
<tag1>
<tag2>foo</tag2>
</tag1>

यदि उपरोक्त आपके प्लेटफ़ॉर्म पर काम नहीं करता है, तो -zइसके अलावा ध्वज को पास करने का प्रयास करें , यह एनआरयू को लाइन विभाजक के रूप में मानने के लिए मजबूर करता है, जिससे पूरी फ़ाइल एक ही लाइन की तरह दिखती है।

grep -ozP '(?s)<tag1>(?:(?!tag1).)*?foo(?:(?!tag1).)*?</tag1>' file.txt

यह ओपी के उदाहरण फ़ाइल पर चलने पर मेरे सिस्टम पर कोई आउटपुट नहीं देता है।
terdon

मेरे लिये कार्य करता है। +1। (?s)टिप के लिए धन्यवाद
नाथन वालेस

@terdon, GNU grep का कौन सा संस्करण चल रहा है?
इरुवर

@ (GNU grep) 2.14डेबियन पर 1_CR । मैंने ओपी उदाहरण की नकल की है (केवल अंतिम नई पंक्ति को जोड़ते हुए) और grepउस पर अपना काम चलाया लेकिन कोई परिणाम नहीं मिला।
terdon

1
@ एसएलएम, मैं पीसीआर 6.6, जीएनयू जीआरईपी 2.5.1 आरएचईएल पर हूं। क्या आपको अपने प्लेटफॉर्म पर काम grep -ozPकरने का मन grep -oPहै?
इरुवर

3
#begin command block
#append all lines between two addresses to hold space 
    sed -n -f - <<\SCRIPT file.xml
        \|<tag1>|,\|</tag1>|{ H 
#at last line of search block exchange hold and pattern space 
            \|</tag1>|{ x
#if not conditional ;  clear buffer ; branch to script end
                \|<tag2>[^<]*foo[^\n]*</tag2>|!{s/.*//;h;b}
#do work ; print result; clear buffer ; close blocks
    s?*?*?;p;s/.*//;h;b}}
SCRIPT

यदि आप ऊपर दिए गए डेटा को देखते हैं, तो उस अंतिम सफाई लाइन से पहले, आपको एक sedपैटर्न स्पेस के साथ काम करना चाहिए जो दिखता है:

 ^\n<tag1>\n<tag2>foo</tag2>\n</tag1>$

जब भी आपको look पसंद हो आप अपना पैटर्न स्पेस प्रिंट कर सकते हैं । फिर आप \nवर्णों पर पता कर सकते हैं ।

sed l <file

आपको दिखाएंगे कि प्रत्येक पंक्ति sedइसे उस चरण में संसाधित करती lहै जिसे कहा जाता है।

इसलिए मैंने अभी इसका परीक्षण किया है और इसे पहली पंक्ति में एक के \backslashबाद एक और अधिक की आवश्यकता है ,comma, लेकिन अन्यथा के रूप में काम करता है। यहां मैंने इसे एक _sed_functionऐसे स्थान पर रखा है जहां मैं इसे पूरे उत्तर में प्रदर्शन उद्देश्यों के लिए आसानी से कह सकता हूं: (इसमें शामिल टिप्पणियों के साथ काम करता है, लेकिन यहां संक्षिप्तता के लिए हटा दिया गया है)

_sed_function() { sed -n -f /dev/fd/3 
} 3<<\SCRIPT <<\FILE 
    \|<tag1>|,\|</tag1>|{ H
        \|</tag1>|{ x
            \|<tag2>[^<]*foo[^\n]*</tag2>|!{s/.*//;h;b}
    s?*?*?;p;s/.*//;h;b}}
#END
SCRIPT
<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>
FILE


_sed_function
#OUTPUT#
<tag1>
 <tag2>foo</tag2>
</tag1>

अब हम स्विच करेंगे pएक के लिए lतो हम देख सकते हैं कि हम क्या के साथ के रूप में हम अपने स्क्रिप्ट को विकसित करने और गैर-सेशन डेमो को दूर काम कर रहे हैं s?हमारे की अंतिम पंक्ति तो sed 3<<\SCRIPTजैसे सिर्फ दिखता है:

l;s/.*//;h;b}}

तो मैं इसे फिर से चलाऊंगा:

_sed_function
#OUTPUT#
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$

ठीक है! तो मैं सही था - यह एक अच्छा एहसास है। अब, आइए हमारे look के चारों ओर फेरबदल करें, यह देखने के लिए कि यह किस रेखा को खींचता है लेकिन हटाता है। हम अपना करंट हटाएंगे lऔर एक को जोड़ेंगे !{block}ताकि ऐसा लगे:

!{l;s/.*//;h;b}

_sed_function
#OUTPUT#
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$

इससे पहले कि हम इसे मिटा दें, यह कैसा दिखता है।

एक आखिरी चीज जो मैं आपको दिखाना चाहता हूं वह है Hपुरानी जगह जैसा कि हम इसे बनाते हैं। कुछ मुख्य अवधारणाएँ हैं, मुझे आशा है कि मैं प्रदर्शित कर सकता हूँ। इसलिए मैं lफिर से अंतिम ook को हटाता हूं और Hअंत में पुरानी जगह में एक झलक जोड़ने के लिए पहली पंक्ति को बदलता हूं :

{ H ; x ; l ; x

_sed_function
#OUTPUT#
\n<tag1>$
\n<tag1>\n <tag2>bar</tag2>$
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$
\n<tag1>$
\n<tag1>\n <tag2>foo</tag2>$
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$

Hपुरानी जगह रेखा चक्र से बच जाती है - इसलिए नाम। तो क्या लोग अक्सर यात्रा करते हैं - ठीक है, जो मैं अक्सर यात्रा करता हूं - वह यह है कि इसका उपयोग करने के बाद इसे हटाने की आवश्यकता है। इस स्थिति में मैं केवल xएक बार परिवर्तन करता हूं , इसलिए होल्ड स्पेस पैटर्न स्पेस और इसके विपरीत हो जाता है और यह परिवर्तन लाइन चक्रों से भी बच जाता है।

इसका प्रभाव यह है कि मुझे अपना होल्ड स्पेस हटाने की जरूरत है जो मेरा पैटर्न स्पेस हुआ करता था। मैं इसके साथ वर्तमान पैटर्न की जगह को पहले साफ कर रहा हूं:

s/.*//

जो बस हर चरित्र का चयन करता है और उसे हटा देता है। मैं उपयोग नहीं कर सकता dक्योंकि इससे मेरी वर्तमान लाइन चक्र समाप्त हो जाएगा और अगली कमांड पूरी नहीं होगी, जो कि मेरी स्क्रिप्ट को बहुत अधिक कचरा कर देगी।

h

यह एक समान तरीके से काम करता है, Hलेकिन यह स्थान को अधिलेखित करता है, इसलिए मैंने अपने रिक्त स्थान के शीर्ष पर अपने रिक्त पैटर्न स्थान की प्रतिलिपि बनाई है, प्रभावी रूप से इसे हटा रहा है। अब मैं बस:

b

बाहर।

और इसी तरह मैं sedस्क्रिप्ट लिखता हूं ।


धन्यवाद @ एसएलएम! तुम एक बहुत ठीक आदमी हो, तुम्हें पता है?
mikeserv

धन्यवाद, अच्छा काम, 3k करने के लिए बहुत जल्दी चढ़ाई, बगल में 5k 8-)
SLM

मुझे पता नहीं, @ एसएलएम Im कम से कम यहाँ सीखने को देखने के लिए शुरुआत कर रहा हूँ - शायद ive इसकी उपयोगिता को बढ़ा देता है। मैं इसके बारे में सोचना होगा। ive मुश्किल से पिछले कुछ हफ़्ते में भी साइट पर आते हैं।
mikeserv

कम से कम 10 कि। अनलॉक करने लायक हर चीज उस स्तर पर है। दूर रखो, 5k काफी जल्दी आ जाएगा।
SLM

1
खैर, @ एसएलएम - वैसे भी आप एक दुर्लभ नस्ल हैं। मैं हालांकि कई उत्तरों के बारे में सहमत हूं। जब कुछ क्यू बंद हो जाता है तो यह मुझे क्यों परेशान करता है। लेकिन वास्तव में ऐसा कम ही होता है। धन्यवाद फिर से, एसएलएम
mikeserv

2

@ jamespfinn का जवाब पूरी तरह से अच्छी तरह से काम करेगा अगर आपकी फ़ाइल आपके उदाहरण के समान सरल है। यदि आपके पास अधिक जटिल स्थिति है जहां <tag1>2 से अधिक लाइनें हो सकती हैं, तो आपको थोड़ी अधिक जटिल चाल की आवश्यकता होगी। उदाहरण के लिए:

$ cat foo.xml
<tag1>
 <tag2>bar</tag2>
 <tag3>baz</tag3>
</tag1>
<tag1>

 <tag2>foo</tag2>
</tag1>

<tag1>
 <tag2>bar</tag2>

 <tag2>foo</tag2>
 <tag3>baz</tag3>
</tag1>
$ perl -ne 'if(/<tag1>/){$a=1;} 
            if($a==1){push @l,$_}
            if(/<\/tag1>/){
              if(grep {/foo/} @l){print "@l";}
               $a=0; @l=()
            }' foo.xml
<tag1>

  <tag2>foo</tag2>
 </tag1>
<tag1>
  <tag2>bar</tag2>

  <tag2>foo</tag2>
  <tag3>baz</tag3>
 </tag1>

पर्ल स्क्रिप्ट आपकी इनपुट फ़ाइल की प्रत्येक पंक्ति को प्रोसेस करेगी और

  • if(/<tag1>/){$a=1;} : चर $a1 यदि कोई ओपनिंग टैग ( <tag1>) पाया जाता है तो सेट होता है।

  • if($a==1){push @l,$_} : प्रत्येक पंक्ति के लिए, यदि $a है1 उस पंक्ति को सरणी में जोड़ें @l

  • if(/<\/tag1>/) : यदि वर्तमान लाइन समापन टैग से मेल खाती है:

    • if(grep {/foo/} @l){print "@l"}: यदि सरणी में सहेजी गई कोई भी रेखा @l(ये रेखाएँ <tag1>और के बीच की हैं)</tag1> ) स्ट्रिंग से मेल खाती हैं foo, की सामग्री को प्रिंट करें @l
    • $a=0; @l=(): सूची खाली करें ( @l=()) और $a0 पर वापस सेट करें ।

यह उस मामले को छोड़कर अच्छी तरह से काम करता है, जहां "फू" वाले एक से अधिक <tag1> हैं। उस स्थिति में यह पहली <tag1> की शुरुआत से आखिरी </ tag1> के अंत तक हर चीज को प्रिंट करता है ...
Den

@den मैं इसे उदाहरण मेरा उत्तर है जो 3 शामिल में दिखाया गया है के साथ परीक्षण <tag1>के साथ fooहै और यह ठीक काम करता है। यह आपके लिए कब विफल होता है?
terdon

ऐसा लगता है कि regex का उपयोग करके गलत पार्सिंग xml :)
Braiam

1

यहाँ एक sedविकल्प है:

sed -n '/<tag1/{:x N;/<\/tag1/!b x};/foo/p' your_file

व्याख्या

  • -n जब तक निर्देश न दिया जाए, तब तक लाइनें न छापें।
  • /<tag1/ पहले उद्घाटन टैग से मेल खाता है
  • :x बाद में इस बिंदु पर कूदने में सक्षम करने के लिए एक लेबल है
  • N अगली पंक्ति को पैटर्न स्पेस (सक्रिय बफर) में जोड़ता है।
  • /<\/tag1/!b xइसका मतलब है कि अगर मौजूदा पैटर्न स्पेस में कोई क्लोजिंग टैग नहीं है, तो xपहले से बने लेबल पर ब्रांच । इस प्रकार हम पैटर्न स्पेस में लाइनों को तब तक जोड़ते रहते हैं जब तक हमें अपना समापन टैग नहीं मिल जाता।
  • /foo/pइसका मतलब है कि यदि वर्तमान पैटर्न स्पेस मेल खाता है foo, तो इसे प्रिंट किया जाना चाहिए।

1

आप इसे जीएनयू जाग के साथ कर सकते हैं, मुझे लगता है कि अंतिम टैग को रिकॉर्ड विभाजक के रूप में माना जाता है जैसे कि एक ज्ञात अंतिम टैग के लिए </tag1>:

gawk -vRS="\n</tag1>\n" '/foo/ {printf "%s%s", $0, RT}'

या अधिक आम तौर पर (अंतिम टैग के लिए एक रेगेक्स के साथ)

gawk -vRS="\n</[^>]*>\n" '/foo/ {printf "%s%s", $0, RT}'

@ टेर्डन पर इसका परीक्षण foo.xml:

$ gawk -vRS="\n</[^>]*>\n" '/foo/ {printf "%s%s", $0, RT}' foo.xml
<tag1>

 <tag2>foo</tag2>
</tag1>

<tag1>
 <tag2>bar</tag2>

 <tag2>foo</tag2>
 <tag3>baz</tag3>
</tag1>

0

यदि आपकी फ़ाइल ठीक उसी प्रकार संरचित है जैसा आपने ऊपर दिखाया है, तो आप -re -B (लाइनों से पहले) और जीआरपी के लिए झंडे का उपयोग कर सकते हैं ... उदाहरण के लिए:

$ cat yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>
$ grep -A1 -B1 bar yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>
$ grep -A1 -B1 foo yourFile.txt 
<tag1>
 <tag2>foo</tag2>
</tag1>

यदि आपका संस्करण grepइसका समर्थन करता है, तो आप सरल -C(संदर्भ के लिए) विकल्प का भी उपयोग कर सकते हैं जो आसपास की एन लाइनों को प्रिंट करता है:

$ grep -C 1 bar yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>

धन्यवाद परंतु नहीं। यह केवल एक उदाहरण है और असली सामान बहुत अप्रत्याशित लगता है ;-)
डेन

1
उस में फू के साथ एक टैग नहीं मिल रहा है, वह सिर्फ फू खोज रहा है और संदर्भ की लाइनें प्रदर्शित कर रहा है
नाथन वालेस

@ नथनवेल्स हाँ, जो ओपी के लिए पूछ रहा था, यह सवाल में दिए गए मामले में पूरी तरह से अच्छी तरह से काम करता है।
terdon

@terdon यह बिल्कुल नहीं है कि सवाल क्या पूछता है। उद्धरण: "मैं <tag1> पढ़ना चाहूंगा अगर इसमें कहीं भी फू शामिल है।" यह समाधान "मैं 'फू' और संदर्भ की 1 पंक्ति पढ़ना चाहूंगा, जहां 'फू' प्रकट होता है।" आपके तर्क के बाद, इस प्रश्न का समान रूप से मान्य उत्तर होगा tail -3 input_file.xml। हां यह इस विशिष्ट उदाहरण के लिए काम करता है, लेकिन यह प्रश्न का एक उपयोगी उत्तर नहीं है।
नाथन वालेस

@NathanWallace मेरी बात यह थी कि ओपी ने विशेष रूप से कहा था कि यह एक वैध XML प्रारूप नहीं है, उस स्थिति में, यह अच्छी तरह से पर्याप्त हो सकता था कि ओपी जिस स्ट्रिंग को खोज रहा था उसके चारों ओर एन लाइन्स को प्रिंट कर सके। उपलब्ध जानकारी के साथ, यह उत्तर काफी सभ्य था।
terdon
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.