मैं एक फ़ाइल (एस) में एक स्ट्रिंग को कैसे बदल सकता हूं?


751

कुछ खोज मानदंडों के आधार पर फाइलों में तारों को बदलना एक बहुत ही सामान्य कार्य है। मैं कैसे कर सकता हूँ

  • वर्तमान निर्देशिका में सभी फ़ाइलों के fooसाथ स्ट्रिंग बदलें bar?
  • उप निर्देशिकाओं के लिए पुनरावर्ती वही करें?
  • केवल तभी बदलें जब फ़ाइल का नाम किसी अन्य स्ट्रिंग से मेल खाता हो?
  • केवल तभी बदलें जब स्ट्रिंग एक निश्चित संदर्भ में पाई जाती है?
  • बदलें कि क्या स्ट्रिंग एक निश्चित लाइन संख्या पर है?
  • एक ही प्रतिस्थापन के साथ कई तार बदलें
  • अलग-अलग प्रतिस्थापन के साथ कई तार बदलें

2
इस विषय पर एक विहित क्यू एंड ए होने का इरादा है (इस मेटा चर्चा को देखें ), कृपया नीचे दिए गए मेरे उत्तर को संपादित करने या अपना खुद का जोड़ने के लिए स्वतंत्र महसूस करें।
terdon

जवाबों:


1009

1. वर्तमान निर्देशिका में सभी फ़ाइलों में एक स्ट्रिंग के सभी आवृत्तियों को दूसरी जगह बदलना:

ये उन मामलों के लिए हैं जहां आप जानते हैं कि निर्देशिका में केवल नियमित फ़ाइलें हैं और आप सभी गैर-छिपी हुई फ़ाइलों को संसाधित करना चाहते हैं। यदि ऐसा नहीं है, तो 2 में दृष्टिकोण का उपयोग करें।

sedइस उत्तर में सभी समाधान GNU मान लेते हैं sed। यदि FreeBSD या OS / X का उपयोग कर रहे हैं, के -iसाथ बदलें -i ''। यह भी ध्यान दें कि -iकिसी भी संस्करण के साथ स्विच के उपयोग के sedकुछ फाइलसिस्टम सुरक्षा निहितार्थ हैं और किसी भी स्क्रिप्ट में अनजाने हैं, जिसे आप किसी भी तरह से वितरित करने की योजना बनाते हैं।

  • केवल इस निर्देशिका में गैर पुनरावर्ती, फाइलें:

    sed -i -- 's/foo/bar/g' *
    perl -i -pe 's/foo/bar/g' ./* 
    

    ( perlफ़ाइल नाम |या स्थान में समाप्त होने वाले के लिए विफल हो जाएगा ) )।

  • इस और सभी उपनिर्देशिकाओं में पुनरावर्ती, नियमित फाइलें ( छिपे हुए सहित )

    find . -type f -exec sed -i 's/foo/bar/g' {} +

    यदि आप zsh का उपयोग कर रहे हैं:

    sed -i -- 's/foo/bar/g' **/*(D.)

    (यदि सूची बहुत बड़ी है तो विफल हो सकता है, zargsचारों ओर काम करना देखें )।

    बैश नियमित फ़ाइलों के लिए सीधे जाँच नहीं कर सकता, एक लूप की आवश्यकता है (ब्रेसिज़ विश्व स्तर पर विकल्प स्थापित करने से बचें):

    ( shopt -s globstar dotglob;
        for file in **; do
            if [[ -f $file ]] && [[ -w $file ]]; then
                sed -i -- 's/foo/bar/g' "$file"
            fi
        done
    )
    

    फ़ाइलों को तब चुना जाता है जब वे वास्तविक फाइलें (-f) होती हैं और वे लिखने योग्य (-w) होती हैं।

2. केवल तभी बदलें जब फ़ाइल का नाम किसी अन्य स्ट्रिंग से मेल खाता है / एक विशिष्ट एक्सटेंशन है / एक निश्चित प्रकार का है आदि:

  • गैर-पुनरावर्ती, केवल इस निर्देशिका में फ़ाइलें:

    sed -i -- 's/foo/bar/g' *baz*    ## all files whose name contains baz
    sed -i -- 's/foo/bar/g' *.baz    ## files ending in .baz
    
  • इस और सभी उपनिर्देशिकाओं में पुनरावर्ती, नियमित फाइलें

    find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +

    यदि आप बैश का उपयोग कर रहे हैं (ब्रेसेस विश्व स्तर पर विकल्प स्थापित करने से बचते हैं):

    ( shopt -s globstar dotglob
        sed -i -- 's/foo/bar/g' **baz*
        sed -i -- 's/foo/bar/g' **.baz
    )
    

    यदि आप zsh का उपयोग कर रहे हैं:

    sed -i -- 's/foo/bar/g' **/*baz*(D.)
    sed -i -- 's/foo/bar/g' **/*.baz(D.)
    

    यह --बताने के लिए sedकि कमांड लाइन में और झंडे नहीं दिए जाएंगे। इससे शुरू होने वाले फ़ाइल नामों से बचाने के लिए यह उपयोगी है -

  • यदि कोई फ़ाइल एक निश्चित प्रकार की है, उदाहरण के लिए, निष्पादन योग्य ( man findअधिक विकल्पों के लिए देखें ):

    find . -type f -executable -exec sed -i 's/foo/bar/g' {} +

    zsh:

    sed -i -- 's/foo/bar/g' **/*(D*)

3. केवल तभी बदलें जब स्ट्रिंग एक निश्चित संदर्भ में मिलती है

  • यदि बाद में एक ही लाइन पर है, तो केवल इसके fooसाथ बदलें :barbaz

    sed -i 's/foo\(.*baz\)/bar\1/' file

    में sed, का उपयोग कर \( \)की बचत होती है जो कुछ भी कोष्ठक में है और आप तो साथ उस तक पहुँच सकते \1। इस विषय के कई रूप हैं, ऐसे नियमित भावों के बारे में अधिक जानने के लिए, यहां देखें ।

  • बदलें fooसाथ barही अगर fooइनपुट फ़ाइल की 3 डी स्तंभ (क्षेत्र) पर पाया जाता है (सफेद स्थान से अलग किए क्षेत्रों कल्पना करते हुए):

    gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file

    ( gawk4.1.0 या नए की जरूरत है )।

  • एक अलग क्षेत्र के लिए बस उपयोग करें $Nजहां Nब्याज के क्षेत्र की संख्या है। एक अलग क्षेत्र विभाजक के लिए ( :इस उदाहरण में) उपयोग करें:

    gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file

    एक और समाधान का उपयोग कर perl:

    perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo 

    नोट: दोनों awkऔर perlसमाधान फ़ाइल में रिक्ति को प्रभावित करेंगे (प्रमुख और अनुगामी रिक्त स्थान को हटा दें, और उन रेखाओं में रिक्त स्थान के अनुक्रम को एक पंक्ति में रूपांतरित करें जो मेल खाते हैं)। किसी भिन्न फ़ील्ड के लिए, वह फ़ील्ड नंबर $F[N-1]जहाँ Nआप चाहते हैं और एक अलग फ़ील्ड सेपरेटर उपयोग के लिए उपयोग करें ( $"=":"आउटपुट फ़ील्ड विभाजक को सेट करता है :):

    perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo 
  • बदलें fooसाथ barही 4 लाइन पर:

    sed -i '4s/foo/bar/g' file
    gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
    perl -i -pe 's/foo/bar/g if $.==4' file
    

4. मल्टीपल रिप्लेस ऑपरेशन: विभिन्न स्ट्रिंग्स के साथ बदलें

  • आप sedआदेशों को जोड़ सकते हैं :

    sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file

    ध्यान रखें कि आदेश मायने रखता है ( sed 's/foo/bar/g; s/bar/baz/g'के fooसाथ स्थानापन्न होगा baz)।

  • या पर्ल आदेश

    perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
  • यदि आपके पास बड़ी संख्या में पैटर्न हैं, तो sedस्क्रिप्ट फ़ाइल में अपने पैटर्न और उनके प्रतिस्थापन को सहेजना आसान है :

    #! /usr/bin/sed -f
    s/foo/bar/g
    s/baz/zab/g
    
  • या, यदि आपके पास उपर्युक्त होने के लिए बहुत अधिक पैटर्न जोड़े हैं, तो आप फ़ाइल से पैटर्न जोड़े (दो अंतरिक्ष अलग पैटर्न, $ पैटर्न और $ प्रतिस्थापन, प्रति पंक्ति) पढ़ सकते हैं:

    while read -r pattern replacement; do   
        sed -i "s/$pattern/$replacement/" file
    done < patterns.txt
    
  • पैटर्न और बड़ी डेटा फ़ाइलों की लंबी सूचियों के लिए यह काफी धीमा होगा, ताकि आप पैटर्न को पढ़ना और sedउनके बजाय एक स्क्रिप्ट बनाना चाहें । निम्नलिखित मानता है कि एक <space> सीमांकक MATCH <space> REPLACE जोड़े की एक सूची को अलग करता है जो फ़ाइल में प्रति-पंक्ति होती है patterns.txt:

    sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <patterns.txt |
    sed -f- ./editfile >outfile
    

    उपरोक्त प्रारूप काफी हद तक मनमाना है और उदाहरण के लिए, MATCH या REPLACE दोनों में से एक <space> के लिए अनुमति नहीं देता है । हालांकि यह विधि बहुत सामान्य है: मूल रूप से, यदि आप एक आउटपुट स्ट्रीम बना सकते हैं जो स्क्रिप्ट की तरह दिखती है , तो आप उस स्क्रिप्ट को स्टड के रूप में निर्दिष्ट करके स्क्रिप्ट के रूप में स्ट्रीम कर सकते हैं ।sedsedsed-

  • आप एक ही तरह से कई लिपियों को जोड़ और समेट सकते हैं:

    SOME_PIPELINE |
    sed -e'#some expression script'  \
        -f./script_file -f-          \
        -e'#more inline expressions' \
    ./actual_edit_file >./outfile
    

    एक POSIX sedआदेश-पंक्ति पर दिखाई देने वाले क्रम में सभी लिपियों को एक में समेट देगा। इनमें से किसी की भी समाप्ति की आवश्यकता नहीं है \n

  • grep उसी तरह काम कर सकते हैं:

    sed -e'#generate a pattern list' <in |
    grep -f- ./grepped_file
    
  • पैटर्न के रूप में निश्चित-तारों के साथ काम करते समय, नियमित अभिव्यक्ति मेटाचैकर्स से बचने के लिए यह अच्छा अभ्यास है । आप इसे आसानी से कर सकते हैं:

    sed 's/[]$&^*\./[]/\\&/g
         s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
    ' <patterns.txt |
    sed -f- ./editfile >outfile
    

5. एकाधिक प्रतिस्थापन संचालन: एक ही स्ट्रिंग के साथ कई पैटर्न बदलें

  • के किसी भी बदलें foo, barया bazके साथfoobar

    sed -Ei 's/foo|bar|baz/foobar/g' file
  • या

    perl -i -pe 's/foo|bar|baz/foobar/g' file

2
@ स्टीफनचैजेलस संपादन के लिए धन्यवाद, यह वास्तव में कई चीजों को ठीक करता है। हालाँकि, कृपया उन सूचनाओं को न हटाएं जो बैश के लिए प्रासंगिक हैं। हर कोई इस्तेमाल नहीं करता zsh। हर तरह से zshजानकारी जोड़ें, लेकिन बैश सामान को हटाने का कोई कारण नहीं है। इसके अलावा, मुझे पता है कि पाठ प्रसंस्करण के लिए शेल का उपयोग करना आदर्श नहीं है, लेकिन ऐसे मामले हैं जहां इसकी आवश्यकता है। मैंने अपनी मूल स्क्रिप्ट के एक बेहतर संस्करण में संपादित किया जो sedवास्तव में पार्स लूप का उपयोग करने के बजाय एक स्क्रिप्ट बनाएगा । यह उपयोगी हो सकता है यदि आपके पास उदाहरण के लिए कई सौ जोड़े पैटर्न हैं।
terdon

2
@terdon, आपका बैश एक गलत है। 4.3 से पहले bash उतरते समय सहानुभूति का पालन करेगा। इसके अलावा बैश के पास (.)क्वालीफाइंग क्वालीफायर के लिए कोई समकक्ष नहीं है इसलिए यहां उपयोग नहीं किया जा सकता है। (आप कुछ याद कर रहे हैं - साथ ही)। लूप के लिए गलत है (गायब -r) और इसका मतलब है कि फाइलों में कई पास बनाने और एक सेड स्क्रिप्ट पर कोई लाभ नहीं है।
स्टीफन चेजलस

7
@terdon और संकेत कमांड के पहले और --बाद में क्या sed -iदर्शाता है?
गीक

5
@ Geek एक POSIX चीज है। यह विकल्पों के अंत को दर्शाता है और आपको शुरू होने वाले तर्क पास करने देता है -। इसका उपयोग यह सुनिश्चित करता है कि कमांड फाइलों पर काम करेगा जैसे कि नाम -foo। इसके बिना, -fएक विकल्प के रूप में पार्स किया जाएगा।
terdon

1
गिट रिपॉजिटरी में कुछ पुनरावर्ती आदेशों को निष्पादित करने में बहुत सावधान रहें। उदाहरण के लिए, इस उत्तर के खंड 1 में दिए गए समाधान वास्तव में एक .gitनिर्देशिका में आंतरिक गिट फाइलों को संशोधित करेंगे , और वास्तव में आपके चेकआउट को गड़बड़ कर देंगे। नाम से विशिष्ट निर्देशिकाओं के भीतर / संचालित करने के लिए बेहतर है।
पिस्टोस

75

एक अच्छा आरpl acement लिनक्स उपकरण है आरपीएल , कि मूल रूप से, डेबियन परियोजना के लिए लिखा गया था तो यह के साथ उपलब्ध है apt-get install rplकिसी भी डेबियन व्युत्पन्न distro में, और दूसरों के लिए हो सकता है, लेकिन अन्यथा आप डाउनलोड कर सकते हैं tar.gzमें फ़ाइल SourgeForge

उपयोग का सरलतम उदाहरण:

 $ rpl old_string new_string test.txt

ध्यान दें कि यदि स्ट्रिंग में रिक्त स्थान हैं तो इसे उद्धरण चिह्नों में संलग्न किया जाना चाहिए। डिफ़ॉल्ट रूप rplसे कैपिटल अक्षरों का ख्याल रखें लेकिन पूर्ण शब्दों का नहीं , लेकिन आप इन डिफॉल्ट्स को विकल्पों -i(अनदेखे मामले) और -w(संपूर्ण शब्दों) के साथ बदल सकते हैं । आप कई फाइलें भी निर्दिष्ट कर सकते हैं :

 $ rpl -i -w "old string" "new string" test.txt test2.txt

या यहां तक ​​कि एक्सटेंशन ( -x) खोज या यहां तक कि निर्देशिका में पुनरावर्ती ( -R) खोजें:

 $ rpl -x .html -x .txt -R old_string new_string test*

तुम भी (शीघ्र) विकल्प के साथ इंटरैक्टिव मोड में खोज / बदल सकते हैं -p:

आउटपुट फ़ाइलों / स्ट्रिंग की संख्या और खोज के प्रकार (मामले / संवेदनशील, संपूर्ण / आंशिक शब्दों में) को दिखाता है, लेकिन यह -q( शांत मोड ) विकल्प, या और भी अधिक वर्बोज़, सूची पंक्ति संख्याओं के साथ मौन हो सकता है -v( क्रिया मोड ) विकल्प के साथ प्रत्येक फ़ाइल और निर्देशिका के मैच ।

अन्य विकल्प जो याद रखने योग्य हैं -e(सम्मान स्कैप्स) जो अनुमति देते हैं regular expressions, इसलिए आप टैब ( \t), नई लाइनें ( \n), आदि भी खोज सकते हैं । यहां तक ​​कि आप अनुमतियों-f को बाध्य करने के लिए उपयोग कर सकते हैं (ज़ाहिर है, केवल तब जब उपयोगकर्ता के पास अनुमतियाँ लिखी हों) और -dसंशोधन काल को संरक्षित करने के लिए ')।

अंत में, यदि आप अनिश्चित हैं कि वास्तव में क्या करेंगे, तो -s( अनुकरण मोड ) का उपयोग करें ।


2
प्रतिक्रिया और सादगी की तुलना में बहुत बेहतर है। मैं बस यह चाहता हूं कि यह फ़ाइल नामों पर अभिनय करने की अनुमति दे, और फिर यह बिल्कुल सही होगा।
काजकाई

1
मुझे -s पसंद है (अनुकरण मोड) :-)
erm3nda

25

कैसे एक खोज करने के लिए और कई फ़ाइलों को बदलने का सुझाव देता है:

आप फाइंड एंड सेड का भी उपयोग कर सकते हैं, लेकिन मुझे लगता है कि पर्ल की यह छोटी लाइन अच्छी तरह से काम करती है।

perl -pi -w -e 's/search/replace/g;' *.php
  • -इसका अर्थ है कोड की निम्नलिखित पंक्ति को निष्पादित करना।
  • -i का अर्थ है जगह-जगह संपादित करना
  • -साथ में चेतावनी लिखिए
  • इनपुट फ़ाइल पर -p लूप, स्क्रिप्ट के लागू होने के बाद प्रत्येक लाइन को प्रिंट करना।

मेरे सर्वश्रेष्ठ परिणाम पर्ल और grep का उपयोग करके आते हैं (यह सुनिश्चित करने के लिए कि फ़ाइल में खोज अभिव्यक्ति है)

perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )

13

आप पूर्व मोड में विम का उपयोग कर सकते हैं:

वर्तमान निर्देशिका में सभी फाइलों में बीआर के साथ स्ट्रिंग ALF को बदलें?

for CHA in *
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

उप निर्देशिकाओं के लिए पुनरावर्ती वही करें?

find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'

केवल तभी बदलें जब फ़ाइल का नाम किसी अन्य स्ट्रिंग से मेल खाता हो?

for CHA in *.txt
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

केवल तभी बदलें जब स्ट्रिंग एक निश्चित संदर्भ में पाई जाती है?

ex -sc 'g/DEL/s/ALF/BRA/g' -cx file

बदलें कि क्या स्ट्रिंग एक निश्चित लाइन संख्या पर है?

ex -sc '2s/ALF/BRA/g' -cx file

एक ही प्रतिस्थापन के साथ कई तार बदलें

ex -sc '%s/\vALF|ECH/BRA/g' -cx file

अलग-अलग प्रतिस्थापन के साथ कई तार बदलें

ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file

13

मैंने इसका उपयोग किया:

grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
  1. सभी फ़ाइलों को सूचीबद्ध करें old_string

  2. रिक्त स्थान के साथ नई पंक्ति बदलें (ताकि फ़ाइलों की सूची को खिलाया जा सके sed

  3. sedपुराने स्ट्रिंग को नए के साथ बदलने के लिए उन फ़ाइलों को चलाएं ।

अद्यतन: उपर्युक्त परिणाम व्हॉट्सएप वाले फ़ाइलनामों पर विफल होंगे। इसके बजाय, उपयोग करें:

grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'


ध्यान दें कि यह विफल हो जाएगा यदि आपके किसी भी फ़ाइल नाम में स्थान, टैब या newlines हैं। उपयोग grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'इसे मनमाने फ़ाइल नामों के साथ सौदा करेगा।
terdon

धन्यवाद दोस्तों। जोड़ा गया अपडेट और पुराने कोड के कारण यह एक दिलचस्प चेतावनी है जो इस व्यवहार से अनजान किसी व्यक्ति के लिए उपयोगी हो सकता है।
o_o_o-- 20

6

एक उपयोगकर्ता के दृष्टिकोण से, एक अच्छा और सरल यूनिक्स टूल जो पूरी तरह से काम करता है qsubst। उदाहरण के लिए,

% qsubst foo bar *.c *.h

मेरी सभी सी फ़ाइलों के fooसाथ बदल देगा bar। एक अच्छी विशेषता यह है कि qsubstयह एक क्वेरी-रिप्लेस करेगा , यानी यह मुझे प्रत्येक घटना को दिखाएगा fooऔर पूछेगा कि मैं इसे बदलना चाहता हूं या नहीं। [आप -goविकल्प के साथ बिना शर्त (कोई पूछ नहीं) को बदल सकते हैं , और अन्य विकल्प हैं, उदाहरण के लिए, -wयदि आप केवल fooतब बदलना चाहते हैं जब यह पूरा शब्द हो।]

इसे कैसे प्राप्त करें: qsubstडेर माउस (मैकगिल से) द्वारा आविष्कार किया गया था और अगस्त 1987 में comp.unix.source 11 (7) पर पोस्ट किया गया था। अद्यतित संस्करण मौजूद हैं। उदाहरण के लिए, NetBSD संस्करण qsubst.c,v 1.8 2004/11/01संकलन करता है और मेरे मैक पर पूरी तरह से चलता है।


2

मुझे कुछ ऐसा चाहिए था जो एक ड्राई-रन का विकल्प प्रदान करे और एक ग्लोब के साथ पुनरावर्ती रूप से काम करेगा, और इसके साथ करने की कोशिश करने के बाद awkऔर sedमैंने हार मान ली और इसके बजाय इसे अजगर में कर दिया।

स्क्रिप्ट रिकर्सिवली सब एक ग्लोब पैटर्न (जैसे मिलान फ़ाइलें खोज --glob="*.html"एक regex के लिए) और प्रतिस्थापन regex के साथ बदलता है:

find_replace.py [--dir=my_folder] \
    --search-regex=<search_regex> \
    --replace-regex=<replace_regex> \
    --glob=[glob_pattern] \
    --dry-run

हर लंबे विकल्प जैसे कि --search-regexएक छोटा विकल्प है, यानी -s-hसभी विकल्पों को देखने के लिए साथ चलें।

उदाहरण के लिए, यह सभी तिथियों 2017-12-31को 31-12-2017निम्न से फ्लिप करेगा :

python replace.py --glob=myfile.txt \
    --search-regex="(\d{4})-(\d{2})-(\d{2})" \
    --replace-regex="\3-\2-\1" \
    --dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re

import argparse

def find_replace(cfg):
    search_pattern = re.compile(cfg.search_regex)

    if cfg.dry_run:
        print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')

    for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
        for filename in fnmatch.filter(files, cfg.glob):

            if cfg.print_parent_folder:
                pardir = os.path.normpath(os.path.join(path, '..'))
                pardir = os.path.split(pardir)[-1]
                print('[%s]' % pardir)
            filepath = os.path.join(path, filename)

            # backup original file
            if cfg.create_backup:
                backup_path = filepath + '.bak'

                while os.path.exists(backup_path):
                    backup_path += '.bak'
                print('DBG: creating backup', backup_path)
                shutil.copyfile(filepath, backup_path)

            with open(filepath) as f:
                old_text = f.read()

            all_matches = search_pattern.findall(old_text)

            if all_matches:

                print('Found {} matches in file {}'.format(len(all_matches), filename))

                new_text = search_pattern.sub(cfg.replace_regex, old_text)

                if not cfg.dry_run:
                    with open(filepath, "w") as f:
                        print('DBG: replacing in file', filepath)
                        f.write(new_text)
                else:
                    for idx, matches in enumerate(all_matches):
                        print("Match #{}: {}".format(idx, matches))

                    print("NEW TEXT:\n{}".format(new_text))

            elif cfg.verbose:
                print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))


if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='''DESCRIPTION:
    Find and replace recursively from the given folder using regular expressions''',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog='''USAGE:
    {0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]

    '''.format(os.path.basename(sys.argv[0])))

    parser.add_argument('--dir', '-d',
                        help='folder to search in; by default current folder',
                        default='.')

    parser.add_argument('--search-regex', '-s',
                        help='search regex',
                        required=True)

    parser.add_argument('--replace-regex', '-r',
                        help='replacement regex',
                        required=True)

    parser.add_argument('--glob', '-g',
                        help='glob pattern, i.e. *.html',
                        default="*.*")

    parser.add_argument('--dry-run', '-dr',
                        action='store_true',
                        help="don't replace anything just show what is going to be done",
                        default=False)

    parser.add_argument('--create-backup', '-b',
                        action='store_true',
                        help='Create backup files',
                        default=False)

    parser.add_argument('--verbose', '-v',
                        action='store_true',
                        help="Show files which don't match the search regex",
                        default=False)

    parser.add_argument('--print-parent-folder', '-p',
                        action='store_true',
                        help="Show the parent info for debug",
                        default=False)

    config = parser.parse_args(sys.argv[1:])

    find_replace(config)

Here स्क्रिप्ट का एक अद्यतन संस्करण है जो विभिन्न रंगों के साथ खोज शब्दों और प्रतिस्थापन को उजागर करता है।


1
मुझे समझ नहीं आ रहा है कि आप कुछ इस जटिल क्यों बनायेंगे। पुनरावर्तन के लिए, बैश के (या आपके शेल के समतुल्य) globstarविकल्प और **ग्लब्स या का उपयोग करें find। एक सूखी चलाने के लिए, बस का उपयोग करें sed। जब तक आप -iविकल्प का उपयोग नहीं करते , यह कोई बदलाव नहीं करेगा। एक बैकअप उपयोग sed -i.bak(या perl -i .bak) के लिए; उन फ़ाइलों के लिए जो मेल नहीं खातीं, उपयोग करें grep PATTERN file || echo file। और दुनिया में आपको शेल को ऐसा करने देने के बजाय अजगर का विस्तार क्यों करना होगा? script.py --glob=foo*सिर्फ के बजाय क्यों script.py foo*?
terdon

1
मेरे क्यों बहुत सरल हैं: (1) सब से ऊपर, डिबगिंग में आसानी; (2) सहायक समुदाय के साथ केवल एक ही अच्छी तरह से प्रलेखित टूल का उपयोग करना (3) न जाने sedऔर awkअच्छी तरह से और उन्हें माहिर करने के लिए अतिरिक्त समय का निवेश करने के लिए तैयार नहीं होना, (4) पठनीयता, (5) यह समाधान गैर-पॉज़िक्स सिस्टम पर भी काम करेगा (ऐसा नहीं है कि मुझे इसकी आवश्यकता है लेकिन कोई और व्यक्ति हो सकता है)।
ccpindra 12

1

ripgrep (कमांड नाम rg) एक grepउपकरण है, लेकिन खोज का समर्थन करता है और साथ ही प्रतिस्थापित करता है।

$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky

$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky


rg इन-प्लेस विकल्प का समर्थन नहीं करता है, इसलिए आपको इसे स्वयं करना होगा

$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky


नियमित अभिव्यक्ति सिंटैक्स और सुविधाओं के लिए रस्ट रेगेक्स प्रलेखन देखें । -Pस्विच सक्षम हो जाएगा PCRE2 स्वाद। rgडिफ़ॉल्ट रूप से यूनिकोड का समर्थन करता है।

$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat

$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg '\p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)

$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.

$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|\w+' -r '[$0]'
[car] bat [cod] map


जैसे grep, -Fविकल्प निश्चित तारों को मिलान करने की अनुमति देगा, एक आसान विकल्प जो मुझे लगता है कि sedइसे भी लागू करना चाहिए।

$ printf '2.3/[4]*6\nfoo\n5.3-[4]*9\n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29


एक और आसान विकल्प है -Uजो बहु-मिलान से सक्षम बनाता है

$ # (?s) flag will allow . to match newline characters as well
$ printf '42\nHi there\nHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi  Day


rg डॉस-शैली फ़ाइलों को भी संभाल सकता है

$ # same as: sed -E 's/\w+(\r?)$/123\1/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123


इसका एक और फायदा rgयह है कि यह इससे भी तेज होने की संभावना हैsed

$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real    0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real    0m0.007s

$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt    
$ time LC_ALL=C sed 's/\bcat\b/dog/g' big.txt > f1
real    0m0.060s
$ time rg --passthru '\bcat\b' -r 'dog' big.txt > f2
real    0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical

$ time LC_ALL=C sed -E 's/\b(\w+)(\s+\1)+\b/\1/g' big.txt > f1
real    0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(\w+)(\s+\1)+' -r '$1' big.txt > f2
real    0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.