Sed - फ़ाइल में किसी शब्द के पहले k इंस्टेंस को बदलें


24

मैं kएक शब्द के केवल पहले उदाहरणों को बदलना चाहता हूं ।

मैं यह कैसे कर सकता हूँ?

उदाहरण के लिए। मान लें कि फ़ाइल foo.txtमें 'linux' शब्द के 100 उदाहरण हैं।

मुझे केवल पहले 50 घटनाओं को बदलना होगा।


1
आप इसका उल्लेख कर सकते हैं: unix.stackexchange.com/questions/21178/…
cuonglm

क्या आपको विशेष रूप से sed की आवश्यकता है, या अन्य उपकरण स्वीकार्य हैं? क्या आपको कमांड लाइन पर काम करने की आवश्यकता है, या एक पाठ संपादक स्वीकार्य है?
evilsoup

कमांड लाइन पर जो कुछ भी काम करता है वह स्वीकार्य है।
नरेन्द्र-चौधरी

जवाबों:


31

पहले खंड में sedएक पंक्ति पर पहले k- आवृत्तियों को बदलने का उपयोग करने का वर्णन है । दूसरा खंड इस दृष्टिकोण को बढ़ाता है कि किसी फ़ाइल में केवल पहले k- आवृत्तियों को बदलने के लिए, भले ही वे किस लाइन पर दिखाई दें।

लाइन उन्मुख समाधान

मानक सेड के साथ, एक लाइन पर किसी शब्द की k- वीं घटना को बदलने के लिए एक कमांड है। यदि k3 है, उदाहरण के लिए:

sed 's/old/new/3'

या, सभी घटनाओं को बदल सकता है:

sed 's/old/new/g'

इनमें से न तो आप चाहते हैं।

GNU sedएक विस्तार प्रदान करता है जो k-th घटना को और उसके बाद सभी को बदल देगा। यदि k 3 है, उदाहरण के लिए:

sed 's/old/new/g3'

ये आप क्या चाहते हैं करने के लिए जोड़ा जा सकता है। पहली 3 घटनाओं को बदलने के लिए:

$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old

जहां \nक्योंकि हम यकीन है कि यह कभी नहीं एक लाइन पर होता है हो सकता है यहाँ उपयोगी है।

स्पष्टीकरण:

हम तीन sedप्रतिस्थापन आदेशों का उपयोग करते हैं :

  • s/\<old\>/\n/g4

    यह जीएनयू विस्तार चौथे और के बाद के सभी घटनाओं को बदलने के लिए oldके साथ \n

    विस्तारित रेगेक्स सुविधा \<का उपयोग किसी शब्द की शुरुआत और शब्द \>के अंत से मेल खाने के लिए किया जाता है। यह आश्वासन देता है कि केवल पूर्ण शब्दों का मिलान किया जाता है। विस्तारित रेगेक्स के लिए -Eविकल्प की आवश्यकता होती है sed

  • s/\<old\>/new/g

    केवल पहली तीन घटनाएं oldबनी हुई हैं और यह उन सभी को बदल देता है new

  • s/\n/old/g

    चौथी और शेष सभी घटनाओं को पहले चरण में oldबदल दिया गया \n। यह उन्हें वापस उनकी मूल स्थिति में लौटाता है।

गैर-जीएनयू समाधान

यदि जीएनयू sed उपलब्ध नहीं है और आप के पहले 3 घटनाओं को बदलना चाहते हैं oldके लिए newहै, तो तीन का उपयोग sकमांड:

$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old

यह अच्छी तरह से काम करता है जब kएक छोटी संख्या होती है लेकिन बड़े पैमाने पर खराब होती है k

चूंकि कुछ गैर-जीएनयू सेड, अर्धविराम के साथ कमांड के संयोजन का समर्थन नहीं करते हैं, इसलिए यहां प्रत्येक कमांड को अपने स्वयं के -eविकल्प के साथ पेश किया गया है । यह सत्यापित करने के लिए भी आवश्यक हो सकता है कि आपका sedशब्द सीमा प्रतीकों का समर्थन करता है, \<और \>

फ़ाइल उन्मुख समाधान

हम पूरी फाइल को पढ़ने के लिए sed बता सकते हैं और फिर प्रतिस्थापन कर सकते हैं। उदाहरण के लिए, oldबीएसडी-स्टाइल सेड के उपयोग की पहली तीन घटनाओं को बदलने के लिए :

sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'

Sed कमांड H;1h;$!d;xपूरी फाइल को पढ़ता है।

क्योंकि उपरोक्त किसी भी GNU एक्सटेंशन का उपयोग नहीं करता है, इसे BSD (OSX) sed पर काम करना चाहिए। ध्यान दें, सोचा, कि इस दृष्टिकोण के लिए sedलंबी लाइनों को संभालना आवश्यक है । जीएनयू sedठीक होना चाहिए। गैर-जीएनयू संस्करण का उपयोग sedकरने वालों को लंबी लाइनों को संभालने की क्षमता का परीक्षण करना चाहिए।

जीएनयू सेड के साथ, हम आगे gबताए गए ट्रिक का उपयोग कर सकते हैं , लेकिन पहले तीन घटनाओं को बदलने के लिए, के साथ \nप्रतिस्थापित किया \x00गया है:

sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'

यह दृष्टिकोण kबड़े होने के साथ-साथ बढ़ता है। हालांकि, यह \x00माना जाता है कि यह आपके मूल स्ट्रिंग में नहीं है। चूंकि चरित्र \x00को बैश स्ट्रिंग में रखना असंभव है , इसलिए यह आमतौर पर एक सुरक्षित धारणा है।


5
यह केवल लाइनों के लिए काम करता है और हर पंक्ति में पहले 4 घटनाओं को बदल देगा

1
@mikeserv उत्कृष्ट विचार! उत्तर अपडेट किया गया।
जॉन 1024

(1) आप जीएनयू और गैर-जीएनयू सेड का उल्लेख करते हैं, और सुझाव देते हैं tr '\n' '|' < input_file | sed …। लेकिन, निश्चित रूप से, जो पूरे इनपुट को एक पंक्ति में परिवर्तित करता है, और कुछ गैर-जीएनयू सेड्स मनमाने ढंग से लंबी लाइनों को संभाल नहीं सकते हैं। (2) आप कहते हैं, "... ऊपर, उद्धृत स्ट्रिंग '|'को किसी भी वर्ण या वर्णों के स्ट्रिंग द्वारा प्रतिस्थापित किया जाना चाहिए, ..." लेकिन आप trकिसी वर्ण को स्ट्रिंग (लंबाई> 1) से बदलने के लिए उपयोग नहीं कर सकते । (३) अपने अंतिम उदाहरण में, आप कहते हैं -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new। यह एक टाइपो के लिए लगता है -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
जी-मैन का कहना है कि 'मोनिका' की बहाली

@ जी-मैन बहुत धन्यवाद! मैंने जवाब अपडेट कर दिया है।
जॉन 1024

यह बहुत बदसूरत है
लुई मैडॉक्स

8

अवाक का उपयोग करना

Awk कमांड का इस्तेमाल शब्द की पहली N घटनाओं को बदलने के लिए किया जा सकता है।
यदि शब्द एक पूर्ण मिलान है, तो आदेश केवल प्रतिस्थापित होंगे।

नीचे दिए गए उदाहरण में, मैं पहले की जगह कर रहा हूँ 27की घटनाओं oldके साथnew

उप का उपयोग करना

awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file

जब तक यह मेल खाता है old, तब तक यह कमांड प्रत्येक क्षेत्र से गुजरता है , यह जाँचता है कि काउंटर 27 से नीचे है, वेतन वृद्धि और लाइन पर पहला मैच स्थानापन्न। फिर अगले फ़ील्ड / लाइन पर जाता है और दोहराता है।

फ़ील्ड को मैन्युअल रूप से बदलना

awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file

कमांड के समान लेकिन इससे पहले ही इसमें एक मार्कर है कि यह किस क्षेत्र पर है ($i), यह केवल फ़ील्ड के मान को बदल देता oldहै new

पहले एक जाँच करना

awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file

जाँच कर रहा है कि लाइन पुरानी है और काउंटर 27 से नीचे है, SHOULDएक छोटी गति बढ़ाने के लिए प्रदान करें क्योंकि यह लाइनों को संसाधित नहीं करेगा जब ये झूठे होते हैं।

परिणाम

उदाहरण के लिए

old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old

सेवा मेरे

new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old

पहले वाला (उप का उपयोग करके) गलत काम करता है यदि स्ट्रिंग "पुराना" पहले से पुराना शब्द है ; उदाहरण के लिए, "बूढ़े व्यक्ति को कुछ सोना दें।" → "बूढ़े आदमी को कुछ दान करें।"
जी-मैन कहते हैं, 'मोनिका'

@ जी-मैन हाँ मैं $iथोड़ा भूल गया , इसका संपादन किया गया है, धन्यवाद :)

7

कहते हैं कि आप एक स्ट्रिंग के केवल पहले तीन उदाहरणों को बदलना चाहते हैं ...

seq 11 100 311 | 
sed -e 's/1/\
&/g'              \ #s/match string/\nmatch string/globally 
-e :t             \ #define label t
-e '/\n/{ x'      \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{'   \ #if not 3 characters in hold space do
-e     's/$/./'   \ #add a new char to hold space
-e      x         \ #exchange hold/pattern spaces again
-e     's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e     'b t'      \ #branch back to label t
-e '};x'          \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g'      #end match function; remove all newline characters

ध्यान दें: उपरोक्त संभवतः अंतर्निहित टिप्पणियों के साथ काम नहीं करेगा
... या मेरे उदाहरण के मामले में, '1' का ...

उत्पादन:

22
211
211
311

वहां मैं दो उल्लेखनीय तकनीकों का उपयोग करता हूं। पहले स्थान 1पर एक पंक्ति की प्रत्येक घटना को प्रतिस्थापित किया जाता है \n1। इस तरह, जैसा कि मैं अगली बार पुनरावर्ती प्रतिस्थापन करता हूं, मुझे यकीन है कि दो बार घटना को प्रतिस्थापित नहीं किया जा सकता है यदि मेरे प्रतिस्थापन स्ट्रिंग में मेरी प्रतिस्थापित स्ट्रिंग है। उदाहरण के लिए, अगर मैं इसके heसाथ प्रतिस्थापित करता हूं, तब heyभी काम करेगा।

मैं इसे पसंद करता हूं:

s/1/\
&/g

दूसरे, मैं hप्रत्येक घटना के लिए एक चरित्र को पुराने स्थान में जोड़कर प्रतिस्थापन की गिनती कर रहा हूं । एक बार जब मैं तीन तक पहुँचता हूं तो कोई घटना नहीं होती है। यदि आप इसे अपने डेटा पर लागू करते हैं और अपनी \{3\}इच्छानुसार कुल प्रतिस्थापन को बदल देते हैं और आपके द्वारा प्रतिस्थापित /\n1/करने के लिए जो भी मतलब है उसके पते, आपको केवल अपनी इच्छानुसार प्रतिस्थापित करना चाहिए।

मैंने केवल -eपठनीयता के लिए सभी सामान किया। POSIXly इसे इस तरह लिखा जा सकता है:

nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"

और w / ग्नू sed:

sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'

यह भी याद रखें कि sedयह लाइन-ओरिएंटेड है - यह पूरी फाइल में नहीं पढ़ता है और फिर इसे वापस लूप करने का प्रयास करता है जैसा कि अन्य संपादकों में अक्सर होता है। sedसरल और कुशल है। उस ने कहा, यह अक्सर निम्नलिखित की तरह कुछ करने के लिए सुविधाजनक है:

यहां एक छोटा शेल फ़ंक्शन है जो इसे एक साधारण निष्पादित कमांड में बंडल करता है:

firstn() { sed "s/$2/\
&/g;:t 
    /\n/{x
        /.\{$(($1))"',\}/!{
            s/$/./; x; s/\n'"$2/$3"'/
            b t
        };x
};s/\n//g'; }

तो इसके साथ ही मैं यह कर सकता हूं:

seq 11 100 311 | firstn 7 1 5

...और पाओ...

55
555
255
311

... या ...

seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'

...लेना...

10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25

... या, अपने उदाहरण से मेल खाने के लिए (परिमाण के एक छोटे से क्रम पर) :

yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux

4

पर्ल में एक छोटा विकल्प:

perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file

`$ N $ के मूल्य को अपनी पसंद के अनुसार बदलें।

यह काम किस प्रकार करता है:

  • हर पंक्ति के लिए, यह स्थानापन्न करने की कोशिश कर रखता है newके लिए old( s/old/new/जब भी यह कर सकते हैं) और, यह चर वृद्धि कर देता है $i( ++$i)।
  • यह लाइन ( 1 while ...) पर तब तक काम करता रहता है जब तक कि यह $nकुल प्रतिस्थापन से कम नहीं हो जाता है और यह उस रेखा पर कम से कम एक प्रतिस्थापन बना सकता है।

4

शेल लूप का उपयोग करें और ex!

{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt

हां, यह थोड़ा नासमझ है।

;)

नोट: यदि oldफ़ाइल में 50 से कम इंस्टेंस हैं, तो यह विफल हो सकता है । (मैंने इसे परीक्षण नहीं किया है।) यदि ऐसा है, तो यह फ़ाइल को अनमॉडिफाइड छोड़ देगा।


बेहतर अभी तक, विम का उपयोग करें।

vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x

स्पष्टीकरण:

q                                # Start recording macro
 q                               # Into register q
  gg                             # Go to start of file
    /old<CR>                     # Go to first instance of 'old'
            :s/old/new/<CR>      # Change it to 'new'
                           q     # Stop recording
                            49@q # Replay macro 49 times

:x  # Save and exit

: s // new <CR> को भी काम करना चाहिए, क्योंकि एक खाली रेगेक्स अंतिम उपयोग की गई खोज को
पुन:

3

Https://stackoverflow.com/questions/148451/how-to-use-sed-sed-to-replace-only-the-first-occurrence-in-a में वर्णित आदेशों पर लूप करने का एक सरल, लेकिन बहुत तेज़ समाधान नहीं है। -file

for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/"  file.txt  ; done

यह विशेष रूप से sed कमान शायद जीएनयू sed और अगर केवल के लिए काम करता newword का हिस्सा नहीं है oldword । गैर-जीएनयू सेड के लिए यहां देखें कि किसी फाइल में केवल पहला पैटर्न कैसे बदलें।


+1 को "बोल्ड" के साथ "पुराने" के स्थान पर पहचानने के लिए समस्या हो सकती है।
जी-मैन का कहना है कि 'मोनिका'

2

जीएनयू के साथ awkआप शब्द विभाजक RSको शब्द सीमाओं द्वारा सीमांकित किए जाने के लिए रिकॉर्ड विभाजक सेट कर सकते हैं। फिर यह शेष रिकॉर्ड के लिए मूल रिकॉर्ड विभाजक को बनाए रखते हुए पहले रिकॉर्ड के लिए प्रतिस्थापन शब्द को आउटपुट पर रिकॉर्ड विभाजक सेट करने का मामला हैk

awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file

या

awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.