एक ही पास में कई तार बदलें


12

मैं कॉम्प्लेक्स फ़ाइल में प्लेसहोल्डर स्ट्रिंग्स को ठोस मूल्यों के साथ बदलने का एक तरीका ढूंढ रहा हूं, जिसमें सामान्य यूनिक्स टूल (बैश, सेड, ऑक, शायद पर्ल) हैं। यह महत्वपूर्ण है कि प्रतिस्थापन एक ही पास में किया जाता है, अर्थात्, जो पहले से ही स्कैन किया गया है / प्रतिस्थापित किया गया है उसे दूसरे प्रतिस्थापन के लिए नहीं माना जाना चाहिए। उदाहरण के लिए, ये दो प्रयास विफल होते हैं:

echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA

echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA

इस मामले में सही परिणाम निश्चित रूप से बी.ए.

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

नोट मैं केवल सही जेनेरिक समाधानों की तलाश में हूं। कृपया उन समाधानों का प्रस्ताव न करें जो कुछ इनपुट्स के लिए विफल होते हैं (इनपुट फ़ाइलें, जोड़े को खोजें और बदलें), हालांकि संभावना नहीं है कि वे लग सकते हैं।


मुझे लगता है कि वे एक चरित्र से अधिक लंबे हैं? इसके लिए आप उपयोग कर सकते हैं tr AB BA
केविन

3
और स्पष्ट रूप से, मुझे आश्चर्य नहीं होगा अगर कोई आपके नोट को थोड़ा असभ्य मानता है।
पेट्रफ

1
जब आप नमूना इनपुट या आउटपुट प्रदान नहीं करते हैं, तो आप "केवल सही समाधान कैसे प्राप्त करें" की उम्मीद करते हैं?
जसोनव्रीयन

1
मुझे डर है कि आपको इसे ठीक वैसे ही करने की आवश्यकता होगी जैसा कि आप इसे बता रहे हैं - शुरुआत से पार्स और आप जाते समय प्रतिस्थापित करें - यानी नियमित अभिव्यक्तियों के साथ नहीं।
पीटर

2
यह एक उचित प्रश्न है, लेकिन उत्तर आपको एक राज्य मशीन पार्सर की आवश्यकता है , जो कि रिसी का उत्तर प्रदान करता है (सही हैकर शैली में, मुझे लगता है)। दूसरे शब्दों में, आप कार्य की जटिलता को कम आंक रहे हैं, "मैं नियमित रूप से भावों के साथ उदारतापूर्वक पार्स (एचटी | एक्स) एमएल चाहता हूं" -> जवाब नहीं है। आप (सिर्फ) sed का उपयोग नहीं कर सकते । आप (बस) awk का उपयोग नहीं कर सकते हैं । AFAIK कोई मौजूदा उपकरण नहीं है जो इसे बॉक्स से बाहर कर देगा। संस के कारनामे, आपको कुछ कोड लिखने होंगे।
गोल्डीलॉक्स

जवाबों:


10

ठीक है, एक सामान्य समाधान। निम्नलिखित बैश फ़ंक्शन को 2kतर्कों की आवश्यकता होती है; प्रत्येक जोड़ी में एक प्लेसहोल्डर और एक प्रतिस्थापन होता है। यह आप पर निर्भर है कि वे स्ट्रिंग को उचित रूप से फंक्शन में पास करें। यदि तर्कों की संख्या विषम है, तो एक अंतर्निहित खाली तर्क जोड़ा जाएगा, जो अंतिम प्लेसहोल्डर की घटनाओं को प्रभावी ढंग से हटा देगा।

न तो प्लेसहोल्डर और न ही प्रतिस्थापन में एनयूएल अक्षर हो सकते हैं, लेकिन आप मानक सी का उपयोग कर सकते हैं \जैसे कि \0अगर आपको NULएस की आवश्यकता है (और फलस्वरूप आपको लिखने की आवश्यकता है \\अगर आप चाहते हैं \)।

इसके लिए मानक बिल्ड टूल्स की आवश्यकता होती है जो एक पॉज़िक्स जैसी प्रणाली (lex और cc) पर मौजूद होना चाहिए।

replaceholder() {
  local dir=$(mktemp -d)
  ( cd "$dir"
    { printf %s\\n "%option 8bit noyywrap nounput" "%%"
      printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
      printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
    } | lex && cc lex.yy.c
  ) && "$dir"/a.out
  rm -fR "$dir"
}

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

उदाहरण रन (संदेह के लिए समय के साथ; यह सिर्फ एक सस्ता-ओ कमोडिटी लैपटॉप है):

$ time echo AB | replaceholder A B B A
BA

real    0m0.128s
user    0m0.106s
sys     0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null

real    0m0.118s
user    0m0.117s
sys     0m0.043s

बड़े इनपुट के लिए यह एक अनुकूलन ध्वज प्रदान करने के लिए उपयोगी हो सकता है cc, और वर्तमान स्थिति अनुकूलता के लिए, इसका उपयोग करना बेहतर होगा c99। एक और अधिक महत्वाकांक्षी कार्यान्वयन प्रत्येक बार उत्पन्न करने के बजाय उत्पन्न निष्पादनों को कैश करने की कोशिश कर सकता है, लेकिन वे उत्पन्न करने के लिए बिल्कुल महंगे नहीं हैं।

संपादित करें

यदि आपके पास tcc है , तो आप एक अस्थायी निर्देशिका बनाने के झंझट से बच सकते हैं, और तेज़ संकलन समय का आनंद ले सकते हैं जो सामान्य आकार के इनपुट पर मदद करेगा:

treplaceholder () { 
  tcc -run <(
  {
    printf %s\\n "%option 8bit noyywrap nounput" "%%"
    printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
    printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
  } | lex -t)
}

$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null

real    0m0.039s
user    0m0.041s
sys     0m0.031s

मुझे यकीन नहीं है कि यह मजाक है या नहीं;)
अम्ब्रोज़ बिज्जक

3
@ambrozbizjak: यह काम करता है, यह बड़े इनपुट के लिए जल्दी है और छोटे इनपुट के लिए स्वीकार्य है। यह उन उपकरणों का उपयोग नहीं कर सकता है जो आप सोच रहे थे, लेकिन वे मानक उपकरण हैं। यह मजाक क्यों होगा?
रिसी

4
एक मजाक नहीं होने के लिए +1! : D
गोल्डीलॉक्स

यह POSIX पोर्टेबल की तरह होगा fn() { tcc ; } <<CODE\n$(gen code)\nCODE\n। क्या मैं पूछ सकता हूँ - यह एक भयानक जवाब है और मैंने इसे पढ़ते ही इसे उखाड़ दिया - लेकिन मुझे समझ नहीं आ रहा है कि शेल ऐरे में क्या हो रहा है? यह क्या करता "${@//\"/\\\"}"है?
मिकसेर

@mikeserv: «प्रत्येक तर्क के लिए एक उद्धृत मूल्य (" $ @ ") के रूप में, (/) एक बैकस्लैश (\\) के साथ एक उद्धरण (\") की सभी (//) घटनाओं को प्रतिस्थापित करें (\ ") »। बैश मैनुअल में पैरामीटर विस्तार देखें।
रिसी

1
printf 'STRING1STRING1\n\nSTRING2STRING1\nSTRING2\n' |
od -A n -t c -v -w1 |
sed 's/ \{1,3\}//;s/\\$/&&/;H;s/.*//;x
     /\nS\nT\nR\nI\nN\nG\n1/s//STRING2/
     /\nS\nT\nR\nI\nN\nG\n2/s//STRING1/
     /\\n/!{x;d};s/\n//g;s/./\\&/g' |
     xargs printf %b

###OUTPUT###

STRING2STRING2

STRING1STRING2
STRING1

ऐसा कुछ हमेशा आपके लक्षित स्ट्रिंग्स के प्रत्येक घटना को केवल एक बार sedप्रति पंक्ति में एक धारा में होने के रूप में होता है । यह सबसे तेज़ तरीका है जिसकी मैं कल्पना कर सकता हूँ कि आप इसे करेंगे। तो फिर, मैं सी नहीं लिखते लेकिन इस करता है मज़बूती से अशक्त सीमांकक संभाल अगर आप इसे चाहते हैं। यह कैसे काम करता है, इसके लिए यह उत्तर देखें । यह किसी के साथ कोई समस्या नहीं विशेष खोल वर्ण या इसी तरह के निहित है - लेकिन यह है ASCII स्थान विशिष्ट, या दूसरे शब्दों में, odएक ही लाइन पर नहीं उत्पादन बहु-बाइट वर्ण और केवल प्रति कोई एक कार्य करेंगे। यदि यह एक ऐसी समस्या है जिसे आप जोड़ना चाहते हैं iconv


+1 क्यों आप कहते हैं कि यह केवल "आपके लक्ष्य तारों की सबसे पहली घटना" की जगह लेता है? आउटपुट में ऐसा लगता है जैसे यह उन सभी को बदल देता है। मैं इसे देखने के लिए नहीं कह रहा हूं, लेकिन क्या इस तरह से मूल्यों को हार्डकोड किए बिना किया जा सकता है?
गोल्डीलॉक्स

@goldilocks - हाँ - लेकिन जैसे ही वे होते हैं। हो सकता है कि मैं इसे फिर से लिखूं। और हाँ - आप बस एक बीच sedमें जोड़ सकते हैं और एक शून्य या कुछ तक बचा सकते हैं, फिर sedइसे एक स्क्रिप्ट लिख सकते हैं; या इसे एक शेल फ़ंक्शन में डालें और इसे प्रति पंक्ति एक मान पर मान दें जैसे "/$1/"... "/$2/"- शायद मैं उन कार्यों को भी
लिखूंगा

यह उस स्थिति में काम नहीं करता है जहाँ प्लेसहोल्डर हैं PLACE1, PLACE2और PLAPLAहमेशा जीतता है। ओपी कहता है: " दिए गए प्रतिस्थापन तारों में से एक के लिए सबसे लंबे मैच के लिए बाएं से दाएं इनपुट को स्कैन करने के बराबर " (जोर दिया गया)
रिची

@rici - धन्यवाद। फिर मुझे अशक्त करना होगा। एक फ्लैश में वापस।
मिकसेर

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

1

एक perlसमाधान। यहां तक ​​कि अगर कुछ ने कहा कि यह संभव नहीं है, तो मैंने एक पाया लेकिन सामान्य रूप से एक साधारण मैच और प्रतिस्थापित करना संभव नहीं है और यहां तक ​​कि यह खराब हो जाता है क्योंकि एनएफए के पीछे होने के कारण परिणाम अप्रत्याशित हो सकता है।

सामान्य तौर पर, और यह कहा जाना चाहिए, समस्या अलग-अलग परिणाम देती है जो प्रतिस्थापन ट्यूपल्स के आदेश और लंबाई पर निर्भर करती है। अर्थात:

A B
AA CC

और इनपुट AAAमें परिणाम है BBBया CCB

यहाँ कोड है:

#!/usr/bin/perl

$v='if (0) {} ';
while (($a,$b)=split /\s+/, <DATA>) {
  $k.=$a.'|';
  $v.='elsif ($& eq \''.$a.'\') {print \''.$b.'\'} ';
}
$k.='.';
$v.='else {print $&;}';

eval "
while (<>) {
  \$_ =~ s/($k)/{$v}/geco;
}";  
print "\n";


__DATA__
A    B
B    A
abba baab
baab abbc
abbc aaba

Checkerbunny:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.