bash - एक Grep RegEx से समूह कैप्चरिंग




shell (5)

फाइलों की एक सरणी को देखने के लिए मुझे sh छोटी (मैक ओएसएक्स 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 ने पाया कि फ़ाइल नाम प्रदान किए गए मामले से मेल खाता है। मैं क्या चाहता हूं कि माता-पिता के अंदर क्या है ([az]+) और एक चर के लिए स्टोर करें

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

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

यदि आपके पास समय है, तो कृपया बताएं कि क्या हो रहा है।


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

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

फिर name मूल्य abc

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


मुझे एहसास है कि इसके लिए एक उत्तर पहले ही स्वीकार कर लिया गया था, लेकिन "कड़ाई से * निक्स शुद्धवादी कोण" से ऐसा लगता है कि नौकरी के लिए सही उपकरण 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 pcregrep संस्करण की तरह काम करता है यदि यह नंगे है, लेकिन यह pcregrep में एक संख्यात्मक पैरामीटर भी स्वीकार करता है, जो इंगित करता है कि आप कौन से कैप्चरिंग समूह को दिखाना चाहते हैं।

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

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


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

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files
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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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





grep