ग्रीप RegEx से समूहों को कैप्चर करना


380

मैं shफ़ाइलों की एक सरणी के माध्यम से देखने के लिए (मैक OSX 10.6) में यह छोटी सी स्क्रिप्ट मिली है । Google ने इस बिंदु पर सहायक होना बंद कर दिया है:

files="*.jpg"
for f in $files
    do
        echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
        name=$?
        echo $name
    done

अब तक (स्पष्ट रूप से, आपको शेल गुरु) $nameकेवल 0, 1 या 2 धारण करता है, जो grepइस बात पर निर्भर करता है कि क्या फाइलनाम उपलब्ध कराए गए मामले से मेल खाता है। मुझे जो पसंद है, वह है जो अंदर के पार्न्स को कैप्चर करना ([a-z]+)और एक वैरिएबल को स्टोर करना है

यदि संभव हो तो मैं केवल उपयोगgrep करना चाहूंगा । यदि नहीं, तो कृपया कोई पायथन या पर्ल, आदि sedया ऐसा कुछ नहीं - मैं खोल के लिए नया हूं और * निक्स प्यूरिस्ट एंगल से इस पर हमला करना चाहूंगा।

इसके अलावा, एक सुपर-कूल बोनु के रूप में, मुझे इस बात की उत्सुकता है कि मैं शेल में स्ट्रिंग कैसे कर सकता हूं? क्या जिस समूह पर मैंने कब्जा किया है, वह $ नाम में संग्रहीत "somename" था, और मैं इसके अंत में स्ट्रिंग ".jpg" जोड़ना चाहता था, क्या मैं कर सकता था cat $name '.jpg'?

यदि आपको समय मिल गया है तो कृपया बताएं।


30
क्या वास्तव में grep sed की तुलना में unix है?
मार्टिन क्लेटन

3
आह, यह सुझाव देने का मतलब नहीं था। मैं बस उम्मीद कर रहा था कि एक उपकरण का उपयोग करके एक समाधान मिल सकता है जो मैं विशेष रूप से यहां सीखने की कोशिश कर रहा हूं। यदि इसका उपयोग करना हल करना संभव नहीं है grep, तो sedबहुत अच्छा होगा, यदि इसका उपयोग करना हल करना संभव है sed
इसहाक

2
मुझे एक :) को उस btw पर रखना चाहिए ...
मार्टिन क्लेटन

Psh, मेरा दिमाग वैसे ही आज तप रहा है।
इसहाक

2
@martinclayton यह एक दिलचस्प तर्क होगा। मुझे वास्तव में लगता है कि sed, (या एड सटीक होने के लिए) अधिक पुराना होगा (और इसलिए purer? हो सकता है?) Unix क्योंकि grep का अर्थ है कि यह एड एक्सप्रेशन g (lobal) / re (gular एक्सप्रेशन) / p (rint) से नाम है।
ffledgling

जवाबों:


499

यदि आप बैश का उपयोग कर रहे हैं, तो आपको उपयोग करने की आवश्यकता नहीं है grep :

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files    # unquoted in order to allow the glob to expand
do
    if [[ $f =~ $regex ]]
    then
        name="${BASH_REMATCH[1]}"
        echo "${name}.jpg"    # concatenate strings
        name="${name}.jpg"    # same thing stored in a variable
    else
        echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
    fi
done

रेगेक्स को एक चर में रखना बेहतर है। कुछ पैटर्न काम नहीं करेगा अगर शाब्दिक रूप से शामिल किया गया हो।

यह उपयोग करता है =~ बैश के रेगेक्स मैच ऑपरेटर का है। मैच के परिणाम नामक एक सरणी में सहेजे जाते हैं $BASH_REMATCH। पहला कैप्चर ग्रुप इंडेक्स 1, दूसरा (यदि कोई हो) को इंडेक्स 2 में रखा गया है, तो इंडेक्स शून्य पूरा मैच है।

आपको पता होना चाहिए कि एंकर के बिना, यह रेगेक्स (और उपयोग करने वाला grep) निम्नलिखित उदाहरणों में से किसी एक और अधिक से मेल खाएगा, जो कि वह नहीं है जो आप खोज रहे हैं:

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz

दूसरे और चौथे उदाहरण को खत्म करने के लिए, अपने regex को इस तरह बनाएं:

^[0-9]+_([a-z]+)_[0-9a-z]*

जो कहता है कि स्ट्रिंग को एक या अधिक अंकों से शुरू करना चाहिए । कैरेट स्ट्रिंग की शुरुआत का प्रतिनिधित्व करता है। यदि आप regex के अंत में एक डॉलर का चिह्न जोड़ते हैं, तो इस तरह:

^[0-9]+_([a-z]+)_[0-9a-z]*$

फिर तीसरा उदाहरण भी समाप्त हो जाएगा क्योंकि डॉट रेगेक्स में पात्रों के बीच नहीं है और डॉलर का चिह्न स्ट्रिंग के अंत का प्रतिनिधित्व करता है। ध्यान दें कि चौथा उदाहरण इस मैच को भी विफल करता है।

यदि आपके पास जीएनयू है grep(लगभग 2.5 या बाद में, मुझे लगता है, जब \Kऑपरेटर जोड़ा गया था):

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg

\Kऑपरेटर (चर लंबाई लुक-पीछे) मैच के लिए पूर्ववर्ती पैटर्न का कारण बनता है, लेकिन परिणाम में मैच शामिल नहीं है। निर्धारित लंबाई समतुल्य है (?<=)- पैटर्न को कोष्ठक बंद करने से पहले शामिल किया जाएगा। आप का उपयोग करना चाहिए \Kअगर परिमाणकों अलग-अलग लंबाई के तार से मेल कर सकते हैं (उदाहरण के लिए +, *, {2,4})।

(?=)ऑपरेटर मैचों निश्चित या चर लंबाई पैटर्न और "रंग-रूप-आगे" कहा जाता है। यह परिणाम में मिलान किए गए स्ट्रिंग को भी शामिल नहीं करता है।

मैच को असंवेदनशील बनाने के लिए, द (?i) ऑपरेटर का उपयोग किया जाता है। यह उन पैटर्नों को प्रभावित करता है जो इसका अनुसरण करते हैं इसलिए इसकी स्थिति महत्वपूर्ण है।

फ़ाइल नाम में अन्य वर्ण हैं या नहीं, इसके आधार पर regex को समायोजित करने की आवश्यकता हो सकती है। आप ध्यान देंगे कि इस मामले में, मैं एक स्ट्रिंग को एक ही समय में बदलने का एक उदाहरण दिखाता हूं कि सबस्ट्रिंग कैप्चर किया गया है।


48
इस उत्तर में मैं विशिष्ट लाइन को अपवोट करना चाहता हूं जो कहती है कि "रेगेक्स को एक चर में रखना बेहतर है। कुछ पैटर्न काम नहीं करेंगे यदि शाब्दिक रूप से शामिल किया गया हो।"
ब्रैंडिन

5
@FrancescoFrassinelli: एक उदाहरण एक पैटर्न है जिसमें सफेद स्थान शामिल है। यह भागने के लिए अजीब है और आप उद्धरण का उपयोग नहीं कर सकते क्योंकि यह एक regex से एक साधारण स्ट्रिंग के लिए मजबूर करता है। एक चर का उपयोग करने का सही तरीका है उद्धरण का उपयोग असाइनमेंट के दौरान चीजों को बहुत सरल बनाने में किया जा सकता है।
अगली सूचना तक रोक दिया गया।

5
/Kऑपरेटर चट्टानों।
razz

2
@ब्रांडन: यह काम करता है। आप किस संस्करण का उपयोग कर रहे हैं? मुझे दिखाओ कि आप क्या कर रहे हैं जो काम नहीं करता है और शायद मैं आपको बता सकता हूं कि क्यों।
अगली सूचना तक रोक दिया गया।

2
@mdelolmo: मेरे जवाब में इसके बारे में जानकारी शामिल है grep। इसे भी ओपी ने स्वीकार किया और काफी उत्थान किया। चढ़ाव के लिए धन्यवाद।
अगली सूचना तक रोक दिया गया।

145

यह वास्तव में शुद्ध के साथ संभव नहीं है grep, कम से कम आम तौर पर नहीं।

लेकिन यदि आपका पैटर्न उपयुक्त है, तो आप grepपहले अपनी लाइन को ज्ञात प्रारूप में कम करने के लिए एक पाइपलाइन के भीतर कई बार उपयोग करने में सक्षम हो सकते हैं , और उसके बाद बस उस बिट को निकालने के लिए जिसे आप चाहते हैं। (हालांकि उपकरण की तरह cutऔरsed अभी तक बेहतर हैं)।

तर्क के लिए मान लीजिए कि आपका पैटर्न थोड़ा सरल था: [0-9]+_([a-z]+)_आप इसे इस तरह से निकाल सकते हैं:

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'

पहला grepआपके समग्र पेटरन से मेल खाने वाली किसी भी रेखा को हटा देगा, दूसरा grep(जो --only-matchingनिर्दिष्ट किया गया है) नाम के अल्फा भाग को प्रदर्शित करेगा। यह केवल इसलिए काम करता है क्योंकि पैटर्न उपयुक्त है: "अल्फा भाग" पर्याप्त विशिष्ट है जो आप चाहते हैं।

(एक तरफ: व्यक्तिगत रूप से मैं grep+ cutका उपयोग करने के लिए जो आप कर रहे हैं उसे प्राप्त करने के लिए उपयोग करेंगे echo $name | grep {pattern} | cut -d _ -f 2। यह cutसीमारेखा पर बंटवारे से खेतों में लाइन को पार्स करने के लिए मिलता है।_ , और केवल फ़ील्ड 2 (फ़ील्ड नंबर 1 से शुरू होता है) देता है।

यूनिक्स दर्शन के पास ऐसे उपकरण हैं जो एक काम करते हैं, और इसे अच्छी तरह से करते हैं, और उन्हें गैर-तुच्छ कार्यों को प्राप्त करने के लिए संयोजित करते हैं, इसलिए मैं तर्क दूंगा कि grep+ sedआदि चीजों को करने का एक और अधिक यूनिक्स तरीका है :-)


3
for f in $files; do name=इको $ एफ | grep -oEi '[0-9] + _ ([az] +) _ [0-9a-z] *' | cut -d _ -f 2 ;अहा!
इसहाक

2
मैं उस "दर्शन" से असहमत हूं। यदि आप बाहरी कमांड को कॉल किए बिना शेल की अंतर्निहित क्षमताओं का उपयोग कर सकते हैं, तो आपकी स्क्रिप्ट प्रदर्शन में बहुत तेज होगी। कुछ उपकरण हैं जो फ़ंक्शन में ओवरलैप करते हैं। उदाहरण grep और sed और awk। वे सभी स्ट्रिंग जोड़तोड़ करते हैं, लेकिन जाग उन सभी के ऊपर खड़ा है क्योंकि यह बहुत अधिक कर सकता है। व्यावहारिक रूप से, उपरोक्त डबल ग्रीप या grep + sed की तरह कमांड के सभी चैनिंग को एक awk प्रक्रिया के साथ करके छोटा किया जा सकता है।
भूतडॉग ghost।

7
@ ghostdog74: यहाँ कोई तर्क नहीं है कि एक साथ बहुत सारे छोटे कामों को पूरा करना आम तौर पर सभी को एक ही जगह करने से कम कुशल है, लेकिन मैं अपने दावे के साथ खड़ा हूं कि यूनिक्स दर्शन एक साथ काम करने वाले बहुत सारे उपकरण हैं। उदाहरण के लिए, केवल अभिलेखागार फ़ाइलों को टार करें, यह उन्हें संपीड़ित नहीं करता है, और क्योंकि यह डिफ़ॉल्ट रूप से STDOUT को आउटपुट करता है आप इसे नेटसीट के साथ नेटवर्क में पाइप कर सकते हैं, या इसे bzip2 के साथ संपीड़ित कर सकते हैं, आदि। जो मेरे मन में सम्मेलन और सामान्य को मजबूत करता है। लोकाचार कि यूनिक्स उपकरण पाइप में एक साथ काम करने में सक्षम होना चाहिए।
रोब डेम

कटौती बहुत बढ़िया है - टिप के लिए धन्यवाद! उपकरण बनाम दक्षता तर्क के लिए, मुझे उपकरण की सरलता पसंद है।
ether_joe

grep के ओ विकल्प के लिए सहारा, जो बहुत मददगार है
मिर्ची

96

मुझे पता है कि इसके लिए एक उत्तर पहले से ही स्वीकार कर लिया गया था, लेकिन "कड़ाई से * निक्स शुद्धतावादी कोण" से ऐसा लगता है कि यह नौकरी के लिए सही उपकरण है pcregrep, जिसका उल्लेख अभी तक नहीं हुआ है। लाइनों को बदलने की कोशिश करें:

    echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
    name=$?

निम्नलिखित के लिए:

    name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')

कैप्चरिंग ग्रुप 1 की केवल सामग्री प्राप्त करने के लिए।

यह pcregrepउपकरण आपके द्वारा पहले से उपयोग किए गए सभी समान सिंटैक्स का उपयोग करता है grep, लेकिन आपके द्वारा आवश्यक कार्यक्षमता को लागू करता है।

पैरामीटर नंगे होने पर संस्करण की -oतरह ही काम करता grepहै, लेकिन यह एक संख्यात्मक पैरामीटर को भी स्वीकार करता है pcregrep, जो इंगित करता है कि किस कैप्चरिंग समूह को आप दिखाना चाहते हैं।

इस समाधान के साथ स्क्रिप्ट में आवश्यक न्यूनतम परिवर्तन है। आप बस एक मॉड्यूलर उपयोगिता को दूसरे के साथ बदलते हैं और मापदंडों को ट्विक करते हैं।

दिलचस्प नोट: आप कई कैप्चर समूहों का उपयोग उस क्रम में कई कैप्चर समूहों को वापस करने के लिए कर सकते हैं जिसमें वे लाइन पर दिखाई देते हैं।


3
pcregrepडिफ़ॉल्ट रूप से उपलब्ध नहीं है Mac OS Xजिसमें ओपी उपयोग करता है
grebneke

4
मेरी pcregrepसमझ में नहीं आ रहा है -o: "-o1" में "अज्ञात विकल्प पत्र '1' के बाद का अंक। इसके अलावा, देखते समय उस फंक्शनलिटी का कोई उल्लेख नहीं हैpcregrep --help
पीटर हेरडेनबर्ग

1
@WAF क्षमा करें, मुझे अपनी टिप्पणी में उस जानकारी को शामिल करना चाहिए। मैं सेंटोस 6.5 पर हूं और पीसीग्रेप संस्करण स्पष्ट रूप से बहुत पुराना है 7.8 2008-09-05:।
पीटर हेरडेनबर्ग

2
हाँ, बहुत मदद, उदाहरण के लिएecho 'r123456 foo 2016-03-17' | pcregrep -o1 'r([0-9]+)' 123456
zhuguowei

5
pcregrep8.41 (के साथ स्थापित apt-get install pcregrepपर Ubuntu 16.03) को नहीं पहचानता -Eiस्विच। यह इसके बिना पूरी तरह से काम करता है, हालांकि। MacOS पर, ऊपर pcregrepसे homebrew(साथ ही 8.41) @ लुप्तप्राय उल्लेख के रूप में स्थापित किया गया है, कम से कम हाई सिएरा -Eस्विच पर भी मान्यता प्राप्त नहीं है।
विले

27

मुझे विश्वास है कि सिर्फ grep में संभव नहीं है

तलछट के लिए:

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`

मैं हालांकि बोनस पर एक छुरा ले जाऊंगा:

echo "$name.jpg"

2
दुर्भाग्य से, वह sedसमाधान काम नहीं करता है। यह बस मेरी निर्देशिका में सब कुछ प्रिंट करता है।
इसहाक

अगर कोई मेल नहीं है, तो एक खाली लाइन का उत्पादन करेगा, इसलिए उसकी जांच अवश्य करें
cobbal

यह अब केवल रिक्त लाइनों को आउटपुट करता है!
इसहाक

इस सेड में समस्या है। कोष्ठक पर कब्जा करने का पहला समूह सब कुछ शामिल करता है। बेशक \ _ कुछ भी नहीं होगा।
भूतडॉग ghost।

इसने कुछ सरल परीक्षण मामलों के लिए काम किया ... \ 2 को आंतरिक समूह मिलता है
कोबाल

16

यह एक समाधान है जो गॉव का उपयोग करता है। यह कुछ ऐसा है जो मुझे लगता है कि मुझे अक्सर उपयोग करने की आवश्यकता है इसलिए मैंने इसके लिए एक फ़ंक्शन बनाया

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }

बस का उपयोग करने के लिए

$ echo 'hello world' | regex1 'hello\s(.*)'
world

महान विचार, लेकिन रेगेक्स में रिक्त स्थान के साथ काम करने के लिए प्रतीत नहीं होता है - उन्हें बदलने की आवश्यकता है \s। क्या आप जानते हैं कि इसे कैसे ठीक किया जाए?
एडम रिक्ज़ोस्की

4

आपके लिए एक सुझाव - आप पिछले अंडरस्कोर से नाम के हिस्से को हटाने के लिए पैरामीटर विस्तार का उपयोग कर सकते हैं, और शुरुआत में भी इसी तरह:

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}

तब nameमूल्य होगा abc

Apple डेवलपर डॉक्स देखें , 'पैरामीटर विस्तार' के लिए आगे खोजें।


यह ([az] +) के लिए जाँच नहीं करेगा।
भूतडोग ghost४

@levislevis - यह सच है, लेकिन, जैसा कि ओपी ने टिप्पणी की है, यह वही करता है जो जरूरी था।
मार्टिन क्लेटन

2

यदि आपके पास बैश है, तो आप विस्तारित ग्लोबिंग का उपयोग कर सकते हैं

shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

या

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

जो पेचीदा लगता है। क्या आप शायद इसके बारे में थोड़ा स्पष्टीकरण बता सकते हैं? या, यदि आप बहुत इच्छुक हैं, तो एक विशेष रूप से व्यावहारिक संसाधन से लिंक करें जो इसे बताता है? धन्यवाद!
इसहाक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.