स्ट्रिंग प्रतिस्थापन के लिए AWK के साथ रेगेक्स का उपयोग कैसे करें?


13

मान लीजिए कि किसी फ़ाइल का कुछ पाठ है:

(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 23" "#2")
("Exercises 31" "#30")
("Notes and References 42" "#34"))
)

मैं "प्रत्येक पंक्ति में एक के बाद 11 जोड़ना चाहता हूं यदि कोई एक है, यानी

(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 34" "#13")
("Exercises 42" "#41")
("Notes and References 53" "#45"))
)

यहाँ GNU AWK और regex का उपयोग करके मेरा समाधान है:

awk -F'#' 'NF>1{gsub(/"(\d+)\""/, "\1+11\"")}'

यानी, मैं बदलना चाहते हैं (\d+)\"के साथ \1+10\", जहां \1समूह का प्रतिनिधित्व कर रहा है (\d+)। लेकिन यह काम नहीं करता है। मैं इसे कैसे कारगर बना सकता हूं?

अगर गौक सबसे अच्छा उपाय नहीं है, तो और क्या इस्तेमाल किया जा सकता है?


नकल के बारे में क्षमा करें। लेकिन मैंने पहले स्टैकओवरफ्लो पर पूछा, और कोई संतोषजनक जवाब नहीं मिला, इसलिए मैंने प्रवास के लिए झंडी दिखा दी। लेकिन यह कुछ समय के लिए नहीं हुआ, इसलिए मुझे इसके होने की उम्मीद नहीं थी और फिर यूनिक्स से पूछा।
टिम

जवाबों:


12

इसे आज़माएं (गौक आवश्यक है)।

awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}' YourFile

अपने उदाहरण के साथ टेस्ट करें:

kent$  echo '(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 2" "#2")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)
'|awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}'   
(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 13" "#13")
("Exercises 41" "#41")
("Notes and References 45" "#45"))
)

ध्यान दें कि यदि दो नंबर (जैसे 1 "और" # 1 ") भिन्न हैं, तो यह कमांड काम नहीं करेगा। या इस पैटर्न के साथ एक ही पंक्ति में अधिक संख्याएँ हैं (उदाहरण 23" ... 32 "..." # 123 ") एक पंक्ति में।


अपडेट करें

चूँकि @Tim (ओपी) ने कहा कि "एक ही पंक्ति में उसके बाद की संख्या अलग हो सकती है, मैंने अपने पिछले समाधान पर कुछ बदलाव किए, और इसे आपके नए उदाहरण के लिए काम किया।

BTW, उदाहरण से मुझे लगता है कि यह सामग्री संरचना की एक तालिका हो सकती है, इसलिए मैं नहीं देखता कि दोनों संख्याएं कैसे भिन्न हो सकती हैं। पहले मुद्रित पृष्ठ संख्या होगी, और # पेज के साथ 2 पेज होगा। क्या मैं सही हू?

वैसे भी, आप अपनी आवश्यकता को सबसे अच्छी तरह से जानते हैं। अब नया समाधान, अभी भी gawk के साथ (मैं इसे पढ़ने के लिए आसान बनाने के लिए पंक्तियों में कमांड तोड़ता हूं):

awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;}
        a=gensub(/.* ([0-9]+)$/,"\\1","g",$1);
        b=gensub(/([0-9]+)\"/,"\\1","g",$2); 
        gsub(/[0-9]+$/,a+11,$1);
        gsub(/^[0-9]+/,b+11,$2);
        print $1,$2
}' yourFile

अपने नए उदाहरण के साथ परीक्षण करें :

kent$  echo '(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 23" "#2")
("Exercises 31" "#30")
("Notes and References 42" "#34"))
)
'|awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;}
        a=gensub(/.* ([0-9]+)$/,"\\1","g",$1);
        b=gensub(/([0-9]+)\"/,"\\1","g",$2); 
        gsub(/[0-9]+$/,a+11,$1);
        gsub(/^[0-9]+/,b+11,$2);
        print $1,$2
}'                        
(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 34" "#13")
("Exercises 42" "#41")
("Notes and References 53" "#45"))
)


EDIT2 @Tim की टिप्पणी पर आधारित है

(1) एफएस = ओएफएस = "\" \ "#" का मतलब इनपुट और आउटपुट दोनों में क्षेत्र का विभाजक है? दो बार दोहरे उद्धरण क्यों निर्दिष्ट करें?

आप इनपुट और आउटपुट दोनों भाग में विभाजक के लिए सही हैं। इसने विभाजक को इस प्रकार परिभाषित किया:

" "#

दो दोहरे उद्धरण हैं, क्योंकि आपके द्वारा वांछित दो संख्याओं को पकड़ना आसान है (आपके उदाहरण इनपुट के आधार पर)।

(2) In.* ([0-9] +) $ /, का अर्थ है स्ट्रिंग का अंत?

बिल्कुल सही!

(३) गेंसब के तीसरे तर्क में (), "जी" और "जी" के बीच क्या अंतर है? G और G में कोई अंतर नहीं है। इसकी जांच करें:

gensub(regexp, replacement, how [, target]) #
    Search the target string target for matches of the regular expression regexp. 
    If "how" is a string beginning with g or G (short for global”), then 
        replace all matches of regexp with replacement.

यह http://www.gnu.org/s/gawk/manual/html_node/String-Functions.html से है । आप gensub का विस्तृत उपयोग प्राप्त करने के लिए पढ़ सकते हैं।


धन्यवाद! मुझे आश्चर्य है कि अगर दो नंबर 1 "और" # 1 "अलग हैं तो यह कैसे काम करेगा?
टिम

यह उत्तर आपके वर्तमान पुनर्खरीद / उदाहरण के लिए काम करता है। यदि आवश्यकता बदल जाती है, तो शायद आप प्रश्न को संपादित कर सकते हैं, और एक बेहतर उदाहरण दे सकते हैं। और आपके कोड से awk -F'#', ऐसा लगता है कि आप केवल '#' के बाद के हिस्से में बदलाव करना चाहते हैं?
केंट

तुम्हारे सुझाव के लिए धन्यवाद। मैंने सिर्फ अपना उदाहरण संशोधित किया ताकि दोनों संख्याएँ समान न हों।
टिम

@ अपने नए उदाहरण के लिए, मेरा अद्यतन उत्तर देखें।
केंट

धन्यवाद! कुछ प्रश्न: (1) का FS=OFS="\" \"#"मतलब है कि इनपुट और आउटपुट दोनों में फ़ील्ड का विभाजक डबल कोट, स्पेस, डबल कोट और # है? दो बार दोहरे उद्धरण क्यों निर्दिष्ट करें? (2) में /.* ([0-9]+)$/, $स्ट्रिंग के अंत का मतलब है? (3) gensub () के तीसरे बहस में, क्या बीच का अंतर है "g"और "G"?
टिम

7

रेगेक्सपी प्रतिस्थापन प्रदान करने वाले हर उपकरण के विपरीत, awk \1प्रतिस्थापन पाठ में बैकरेफेरेंस की अनुमति नहीं देता है । अगर आप का उपयोग जीएनयू Awk मिलान किया समूहों के लिए पहुँच देता है matchसमारोह है, लेकिन साथ नहीं ~या subया gsub

यह भी ध्यान दें कि यदि \1समर्थन किया गया था, तो भी आपका स्निपेट स्ट्रिंग को जोड़ देगा +11, संख्यात्मक गणना नहीं करेगा। इसके अलावा, आपका regexp बिलकुल सही नहीं है, आप चीजों की तरह मिलान कर रहे हैं "42""और नहीं "#42"

यहाँ एक अजीब समाधान है (चेतावनी, अप्राप्त)। यह केवल प्रति पंक्ति एक एकल प्रतिस्थापन करता है।

awk '
  match($0, /"#[0-9]+"/) {
    n = substr($0, RSTART+2, RLENGTH-3) + 11;
    $0 = substr($0, 1, RSTART+1) n substr($0, RSTART+RLENGTH-1)
  }
  1 {print}'

यह पर्ल में सरल होगा।

perl -pe 's/(?<="#)[0-9]+(?=")/$1+11/e'

आपके उत्तर का पहला वाक्य वही है, जिसकी मुझे तलाश थी। हालांकि, इस तथ्य को आपने "... प्रतिस्थापन पाठ में" एक अनुवर्ती सवाल उठाया है: क्या awk regex पैटर्न में बैकरेफेरेंस की अनुमति देता है?
वाइल्डकार्ड

1
@Wildcard नहीं, awk सिर्फ समूहों का ट्रैक नहीं रखता (जीएनयू एक्सटेंशन को छोड़कर मैं उल्लेख करता हूं)।
गिल्स एसओ- बुराई को रोकना '

5

awkयह कर सकते हैं, लेकिन यह प्रत्यक्ष नहीं है, यहां तक ​​कि backreferencing का उपयोग भी किया जा सकता है। गेनूब के रूप में
GNU awk में (आंशिक) बैकरेफेरिंग है

के उदाहरण 123"अस्थायी रूप से में लिपटे रहे हैं \x01और \x02उन्हें असंशोधित रूप में (के लिए चिह्नित करने के लिए sub()। सह

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

awk '{$0=gensub(/([0-9]+)\"/, "\x01\\1\"\x02", "g", $0 )
      while ( match($0, /\x01[0-9]+\"\x02/) ) {
        temp=substr( $0, RSTART, RLENGTH )
        numb=substr( temp, 2, RLENGTH-3 ) + 11
        sub( /\x01[0-9]+\"\x02/, numb "\"" ) 
      } print }'

यहाँ एक और तरीका है, उपयोग gensubऔर सरणी splitऔर \x01एक क्षेत्र परिसीमन के रूप में ( विभाजन के लिए ) .. \ x02 अंकगणित जोड़ के लिए एक उम्मीदवार के रूप में एक सरणी तत्व को चिह्नित करता है।

awk 'BEGIN{ ORS="" } {
     $0=gensub(/([0-9]+)\"/, "\x01\x02\\1\x01\"", "g", $0 )
     split( $0, a, "\x01" )
     for (i=0; i<length(a); i++) { 
       if( substr(a[i],1,1)=="\x02" ) { a[i]=substr(a[i],2) + 11 }
       print a[i]
     } print "\n" }'

धन्यवाद! आपके पहले कोड में, (1) का क्या "\x01\\1\"\x02"मतलब है? मैं अभी भी समझ में नहीं आता \x01और \x02। (2) वापसी किस तरह भिन्न है $0द्वारा gensubऔर $0अंतिम तर्क के रूप में करने के लिए gensub?
टिम

@Tim। हेक्स मान \x01और \x02प्रतिस्थापन मार्कर के रूप में उपयोग किया जाता है। ये मान किसी भी सामान्य पाठ फ़ाइल में होने की अत्यधिक संभावना नहीं है , इसलिए वे समान रूप से उपयोग करने के लिए "अत्यधिक" सुरक्षित हैं (अर्थात, पहले से मौजूद लोगों के साथ एक टकराव नहीं है) .. वे सिर्फ अस्थायी लेबल हैं .. पुनः .. यह देखें लिंक स्ट्रिंग-हेरफेर कार्य , लेकिन सारांश में: यह (gensub) फ़ंक्शन के परिणाम के रूप में संशोधित स्ट्रिंग लौटाता है और मूल लक्ष्य स्ट्रिंग नहीं बदला जाता है। ... बस मूल लक्ष्य को संशोधित करता है ..$0=gensub(... $0)$0=
पीटर

3

चूंकि (जी) awk में समाधान काफी जटिल होते हैं, मैं पर्ल में एक वैकल्पिक समाधान जोड़ना चाहता था:

perl -wpe 's/\d+(?=")/$&+11/eg' < in.txt > out.txt

स्पष्टीकरण:

  • विकल्प -wचेतावनी को सक्षम करता है (जो आपको संभावित अवांछित प्रभावों की चेतावनी देगा)।
  • विकल्प का -pतात्पर्य कोड के चारों ओर एक लूप होता है जो sed या awk के समान काम करता है, इनपुट की प्रत्येक लाइन को डिफ़ॉल्ट चर में स्वचालित रूप से सहेजता है $_
  • ऑप्शन -eपर्ल को बताता है कि प्रोग्राम कोड कमांड लाइन पर चल रहा है, स्क्रिप्ट फ़ाइल में नहीं।
  • कोड एक रेगेक्स प्रतिस्थापन ( s/.../.../) है $_, जहां अंकों का एक अनुक्रम, यदि यह एक के बाद है ", तो अनुक्रम द्वारा प्रतिस्थापित किया जाएगा, इसके अलावा, संख्या 11 के रूप में व्याख्या की गई है।
  • शून्य चौड़ाई सकारात्मक लुक-आगे दावे (?=pattern) के लिए दिखता है ", मैच में ले जा रहा है ताकि हम प्रतिस्थापन में दोहराने की जरूरत नहीं है के बिना। $&प्रतिस्थापन में MATCH चर में केवल संख्या होगी।
  • /eरेगेक्स का संशोधक perlप्रतिस्थापन को एक स्ट्रिंग के रूप में लेने के बजाय कोड के रूप में "निष्पादित" करना बताता है।
  • /gसंशोधक लाइन में हर मैच पर यह दोहरा, "वैश्विक" प्रतिस्थापन बनाता है।

MATCH चर $&दुर्भाग्य से 5.20 से पहले पर्ल संस्करणों में कोड प्रदर्शन के लिए हानिकारक होगा। $1इसके बजाय एक तेज़ (और अधिक जटिल नहीं) समाधान समूहीकरण और पीछे हटने का उपयोग करेगा :

perl -wpe 's/(\d+)?="/$1+11/eg' < in.txt > out.txt

और अगर लुक-फॉरवर्ड अभिकथन बहुत भ्रामक लगता है, तो आप उद्धरण चिह्नों को भी स्पष्ट रूप से बदल सकते हैं:

perl -wpe 's/(\d+)"/$1+11 . q{"}/eg' < in.txt > out.txt
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.