किसी फ़ाइल के कुछ वर्गों को फ़िल्टर या पाइप करना


14

मेरे पास कुछ खंडों के साथ एक इनपुट फ़ाइल है, उदाहरण और शुरुआत के साथ सीमांकित हैं, उदाहरण के लिए:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

मैं इस फ़ाइल में एक परिवर्तन लागू करना चाहता हूं, जैसे कि X, Y, Z कुछ कमांड ( nlउदाहरण के लिए) के माध्यम से फ़िल्टर किए जाते हैं , लेकिन शेष लाइनें अपरिवर्तित से गुजरती हैं। ध्यान दें कि nl(संख्या रेखाएं) रेखाओं के पार स्थिति जमा करती हैं, इसलिए यह एक स्थैतिक परिवर्तन नहीं है जो प्रत्येक लाइन X, Y, Z पर लागू किया जा रहा है। ( संपादित करें : यह इंगित किया गया था कि nlएक ऐसे मोड में काम कर सकते हैं जिसमें संचित राज्य की आवश्यकता नहीं है, लेकिन मैं सिर्फ nlएक उदाहरण के रूप में प्रश्न को सरल बनाने के लिए उपयोग कर रहा हूं । वास्तव में कमांड एक अधिक जटिल कस्टम स्क्रिप्ट है। मैं वास्तव में क्या देख रहा हूंएक इनपुट फ़ाइल के उप-मानक के लिए एक मानक फ़िल्टर लागू करने की समस्या का एक सामान्य समाधान है )

आउटपुट जैसा दिखना चाहिए:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D

फ़ाइल में कई ऐसे अनुभाग हो सकते हैं जिनमें परिवर्तन की आवश्यकता होती है।

अपडेट 2 मैंने मूल रूप से निर्दिष्ट नहीं किया कि क्या होना चाहिए अगर एक से अधिक अनुभाग हैं, उदाहरण के लिए:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
 @@inline-code-start
line L
line M
line N
@@inline-code-end

मेरी अपेक्षा यह होगी कि राज्य को केवल दिए गए अनुभाग के भीतर बनाए रखने की आवश्यकता होगी:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D
     1 line L
     2 line M
     3 line N

लेकिन, मुझे लगता है कि समस्या की व्याख्या करते हुए राज्य को वर्गों में रखने की आवश्यकता मान्य है, और कई संदर्भों में उपयोगी है।

अंत अद्यतन २

मेरा पहला विचार एक सरल राज्य मशीन का निर्माण करना है जो यह बताता है कि हम किस सेक्शन में हैं:

#!/usr/bin/bash
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
  echo $line | nl
  else
    # output
    echo $line
  fi
done

जो मैं साथ चलाता हूं:

cat test-inline-codify | ./inline-codify

प्रत्येक कॉल के nlस्वतंत्र होने के बाद से यह काम नहीं करता है, इसलिए लाइन संख्या में वृद्धि नहीं होती है:

line A
line B
     1  line X
     1  line Y
     1  line Z
line C
line D

मेरा अगला प्रयास पंद्रह का उपयोग करना था:

#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
    echo $line > myfifo
  else
    # output
    echo $line
  fi
done
rm myfifo

यह सही आउटपुट देता है, लेकिन गलत क्रम में:

line A
line B
line C
line D
     1  line 1
     2  line 2
     3  line 3

वहाँ शायद कुछ कैशिंग चल रहा है।

क्या मैं इस सब के बारे में गलत हूँ? यह एक बहुत सामान्य समस्या की तरह लगता है। मुझे लगता है कि वहाँ एक सरल पाइपलाइन होना चाहिए जो इसे हल करेगा।


nlराज्य को जमा नहीं करना है । देखो पर nl -dऔर अपने चेक man/ infoके बारे में जानकारी के लिए पेज nlके खंड सीमांकक
मिकसरोव

nl सिर्फ एक उदाहरण है। मेरे मामले में मैं nl के बजाय एक कस्टम स्क्रिप्ट चला रहा हूं।
जेम्स स्क्रिपवेन

उस स्थिति में, कृपया स्पष्ट करें कि आपकी स्क्रिप्ट क्या कर रही है।
terdon

मैंने सवाल में स्पष्ट किया कि मैं केवल nlएक उदाहरण फिल्टर के रूप में उपयोग कर रहा हूं । मुझे लगा कि यह इस बात का विवरण सरल कर देगा कि वास्तव में फ़िल्टर क्या कर रहा था, लेकिन मैंने शायद सिर्फ और अधिक भ्रम पैदा किया। वास्तव में, मैं एक घर के लिए स्थैतिक ब्लॉग जनरेटर के लिए एक कोड हाइलाइटर के माध्यम से उपधारा छान रहा हूँ। अभी मैं ग्नू का उपयोग कर रहा हूं source-highlight, लेकिन यह बदल सकता है, और मैं और फिल्टर भी जोड़ सकता हूं, जैसे कि फॉर्मेटर भी।
जेम्स स्क्रिपवेन

जवाबों:


7

मैं तुम्हारे साथ सहमत होगा - यह शायद है एक सामान्य समस्या है। कुछ सामान्य उपयोगिताओं में इसे संभालने के लिए कुछ सुविधाएं हैं, हालांकि।


nl

nlउदाहरण के लिए, में इनपुट को अलग करती है तार्किक पृष्ठों के रूप में -dएक दो चरित्र द्वारा elimited खंड सीमांकक । अकेले एक लाइन पर तीन घटनाएँ एक हेडिंग , दो बॉडी और एक पाद की शुरुआत का संकेत देती हैं । यह आउटपुट में एक रिक्त लाइन के साथ इनपुट में पाए गए इनमें से किसी को भी बदल देता है - जो केवल रिक्त लाइनें हैं जो यह कभी प्रिंट करता है

मैंने आपके उदाहरण को बदलकर एक और अनुभाग शामिल किया और इसे अंदर डाल दिया ./infile। तो यह इस तरह दिखता है:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

फिर मैंने निम्नलिखित भाग किया:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nlकहा जा सकता है कि तार्किक पृष्ठों पर राज्य जमा कर सकते हैं , लेकिन यह डिफ़ॉल्ट रूप से नहीं होता है। इसके बजाय यह शैलियों के अनुसार , और अनुभाग द्वारा इसके इनपुट की पंक्तियों को संख्या देगा । तो -haइसका मतलब है कि सभी हेडर लाइन्स और बॉडी लाइन्स-bn का कोई मतलब नहीं है - जैसा कि बॉडी स्टेट में शुरू होता है ।

जब तक मैंने यह नहीं सीखा, तब तक मैं nlकिसी भी इनपुट के लिए उपयोग करता था, लेकिन यह समझने के बाद कि nlइसके डिफॉल्ट -dएलिमिटर के अनुसार आउटपुट विकृत हो सकता है, \:मैंने इसके साथ अधिक सावधान रहना सीखा और grep -nF ''इसके बजाय अनटाइटेड इनपुट का उपयोग करना शुरू कर दिया । लेकिन उस दिन एक और सबक सीखा गया था कि nlइसे अन्य मामलों में बहुत उपयोगी रूप से लागू किया जा सकता है - जैसे कि यह एक - यदि आप केवल इसके इनपुट को थोड़ा संशोधित करते हैं - जैसा कि मैं sedऊपर करता हूं ।

आउटपुट

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

यहाँ कुछ और के बारे में है nl- क्या आप ऊपर सभी रेखाओं को देखते हैं लेकिन गिने हुए स्थान रिक्त स्थान से शुरू होते हैं? जब nlसंख्याएँ रेखाएँ होती हैं तो यह प्रत्येक के सिर में एक निश्चित संख्या में वर्ण सम्मिलित करती है। उन पंक्तियों के लिए यह संख्या नहीं है - यहां तक ​​कि रिक्त - यह हमेशा -wअनिर्दिष्ट -sलाइनों के सिर पर ( idth count + eparator len) * रिक्त स्थान डालकर इंडेंट से मेल खाता है । यह आपको गिने-चुने सामग्री को पुन: क्रमांकित सामग्री की तुलना करके - और थोड़े प्रयास से पुन: प्रस्तुत करने की अनुमति देता है। जब आप विचार करते हैं कि nlइसके इनपुट को आपके लिए तार्किक खंडों में विभाजित किया जाएगा, और यह कि आप -sप्रत्येक पंक्ति के शीर्ष पर मनमाना ट्रिंग्स सम्मिलित कर सकते हैं , तो यह अपने आउटपुट को संभालने में बहुत आसान हो जाता है:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

उपरोक्त प्रिंट ...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

जीएनयू sed

यदि nlआपका लक्ष्य एप्लिकेशन नहीं है, तो एक मैच के आधार पर एक GNU आपके लिए एक मनमाना शेल कमांड xecute sedकर सकता है e

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

ऊपर sedपैटर्न स्पेस में इनपुट एकत्र करता है जब तक कि यह प्रतिस्थापन Tस्थापन को सफलतापूर्वक पारित करने और हाबिल bको वापस रोकना पर्याप्त नहीं है :l। जब ऐसा होता है, तो यह इनपुट के साथ executes का nlप्रतिनिधित्व करता है, <<यहां इसके बाकी सभी पैटर्न-स्पेस के लिए दस्तावेज़ के रूप में दर्शाया गया है।

वर्कफ़्लो इस तरह है:

  1. /^@@.*start$/!b
    • अगर एक ^पूरी पंक्ति $है !नहीं /से मेल /ऊपर पैटर्न, तो यह है bस्क्रिप्ट से बाहर ranched और autoprinted - तो इस बिंदु से पर हम केवल जो पैटर्न के साथ शुरू हुआ लाइनों की एक श्रृंखला के साथ काम कर रहे हैं।
  2. s//nl <<\\@@/
    • खाली s//फ़ील्ड मिलान के /लिए अंतिम पते के लिए खड़ा है sed- इसलिए यह कमांड इसके बजाय पूरी @@.*startलाइन को प्रतिस्थापित करता है nl <<\\@@
  3. :l;N
    • :आदेश एक शाखा लेबल को परिभाषित करता है - यहाँ मैं एक का नाम सेट :lहाबिल। NExt आदेश अगले एक के बाद पैटर्न अंतरिक्ष के लिए इनपुट की रेखा भी संलग्न \newline चरित्र। यह \nएक sedपैटर्न स्पेस में ewline प्राप्त करने के लिए केवल कुछ तरीकों में से एक है - \newline वर्ण एक व्युत्पन्न के लिए एक निश्चित सीमांकक है sedजो इसे थोड़ी देर कर रहा है।
  4. s/\(\n@@\)[^\n]*end$/\1/
    • s///एक शुरुआत के बाद और केवल एक अंतिम पंक्ति की पहली घटना के बाद ही यह ubstration सफल हो सकता है । यह केवल एक पैटर्न स्पेस पर कार्य करेगा जिसमें पैटर्न स्पेस के बहुत अंत को चिह्नित करते हुए \nतुरंत अंतिम ईवलाइन का पालन किया जाता है। जब यह कार्य करता है, तो यह पूरे मिलान वाले स्ट्रिंग को पहले समूह के साथ बदल देता है , या ।@@.*end$\1\(\)\n@@
  5. Tl
    • Tकिसी लेबल की स्था आदेश शाखाओं (यदि उपलब्ध) अगर एक सफल प्रतिस्थापन पिछली बार एक इनपुट लाइन पैटर्न अंतरिक्ष में खींचा गया था के बाद से नहीं हुआ (मैं w / कर के रूप में N) । इसका मतलब यह है कि हर बार एक \nईवालाइन को पैटर्न स्पेस से जोड़ा जाता है जो आपके अंतिम सीमांकक से मेल नहीं खाता है, एस्ट्रल Tकमांड विफल रहता है और पीछे की ओर शाखाएं होती है :l, जिसके परिणामस्वरूप एक्सट्रीम लाइन में sedखींचने Nऔर सफल होने तक लूपिंग होता है।
  6. e

    • जब अंत मैच के लिए प्रतिस्थापन सफल होता है और स्क्रिप्ट एक असफल Tएस्ट के लिए वापस शाखा नहीं करती है, तो इस तरह से आने वाले एक कमांड को एक्सक्यूट sedकरेगा :el

      nl <<\\@@\nline X\nline Y\nline Z\n@@$

आप इसे देखने के लिए वहां अंतिम पंक्ति को संपादित करके अपने लिए देख सकते हैं Tl;l;e

यह प्रिंट करता है:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

ऐसा करने का एक आखिरी तरीका, और शायद सबसे सरल तरीका है, while readलूप का उपयोग करना , लेकिन अच्छे कारण के लिए। शेल - (विशेष रूप से एक bashशेल) - आमतौर पर बड़ी मात्रा में या स्थिर धाराओं में इनपुट से निपटने में बहुत ही संक्षिप्त है। यह भी समझ में आता है, शेल का काम इनपुट चरित्र को चरित्र द्वारा संभालना है और अन्य कमांड को कॉल करना है जो बड़े सामान को संभाल सकता है।

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

हालांकि, एक उदाहरण का उपयोग कैसे किया जा सकता है read और सिंक में इनपुट को संसाधित करने के लिए अन्य आदेश कैसे हो सकते हैं :

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

प्रत्येक पुनरावृत्ति के लिए होने वाली पहली चीज readएक रेखा में खींचती है। यदि यह सफल होता है तो इसका अर्थ है कि लूप अभी तक ईओएफ से नहीं टकराया है और इसलिए caseयह एक स्टार्ट सीमांकक से मेल खाता है , doब्लॉक को तुरंत निष्पादित किया जाता है। एल्स, इसे printfप्रिंट करता $lineहै readऔर sedइसे कहा जाता है।

sedpहर लाइन को तब तक रिंट करेगा, जब तक कि यह स्टार्ट मार्कर से न मिल जाए - जब यह qपूरी तरह से इनपुट का उपयोग करता है। -uNbuffered स्विच जीएनयू के लिए आवश्यक है sed, क्योंकि यह नहीं बल्कि लालच से अन्यथा बफ़र सकते हैं, लेकिन - कल्पना के अनुसार - अन्य POSIX sedरों किसी विशेष विचार किए बिना काम करना चाहिए - जब तक कि <infileएक नियमित रूप से फ़ाइल है।

जब पहला sed quits, शेल doलूप के ब्लॉक को निष्पादित करता है - जो एक और कॉल करता है जो sedहर लाइन को प्रिंट करता है जब तक कि यह अंतिम मार्कर का सामना नहीं करता । यह इसके आउटपुट को पाइप करता है paste, क्योंकि यह प्रत्येक लाइन को अपनी लाइन पर प्रिंट करता है। ऐशे ही:

1
line M
2
line N
3
line O

pasteतब उन :वर्णों पर एक साथ चिपकाया जाता है , और संपूर्ण आउटपुट जैसा दिखता है:

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

ये केवल उदाहरण हैं - कुछ भी या तो परीक्षण में किया जा सकता है या यहां ब्लॉक कर सकते हैं, लेकिन पहली उपयोगिता को बहुत अधिक इनपुट का उपभोग नहीं करना चाहिए।

इसमें शामिल सभी उपयोगिताओं ने एक ही इनपुट पढ़ा - और अपने परिणामों को प्रिंट किया - प्रत्येक अपनी बारी में। इस तरह की चीज को लटका पाना मुश्किल हो सकता है - क्योंकि विभिन्न उपयोगिताओं में दूसरों की तुलना में अधिक बफर होगा - लेकिन आप आम तौर पर भरोसा कर सकते हैंdd , headऔर sedसही काम करने के लिए (हालांकि, जीएनयू के लिए sed, आप CLI-स्विच की जरूरत है) और आपको हमेशा भरोसा करना चाहिए read- क्योंकि यह स्वभाव से, बहुत धीमा है । और यही कारण है कि उपरोक्त लूप प्रति इनपुट ब्लॉक को केवल एक बार कॉल करता है।


मैंने sedआपके द्वारा दिए गए दूसरे उदाहरण का परीक्षण किया , और यह काम करता है, लेकिन मैं वास्तव में वाक्यविन्यास को बनाने में परेशानी कर रहा हूं। (मेरी सीड बहुत कमज़ोर है और आमतौर पर s / findthis / replacethis / g तक सीमित है। मुझे बैठने के लिए प्रयास करना होगा और वास्तव में sed समझना होगा।)
जेम्स स्क्रिप्‍न

@JamesScriven - मैंने इसे बेहतर तरीके से समझाने के लिए संपादित किया है। मुझे पता है अगर यह मदद नहीं करता है। मैंने कमांड को बहुत बदल दिया - यह अब छोटे, अधिक समझदार टुकड़ों में है।
mikeserv

4

एक संभावना विम पाठ संपादक के साथ ऐसा करने की है। यह शेल कमांड के माध्यम से मनमाने वर्गों को पाइप कर सकता है।

ऐसा करने का एक तरीका लाइन नंबर का उपयोग करके है :4,6!nl। यह पूर्व कमांड 4-6 समावेशी की तर्ज पर nl चलाएगा, जो आप अपने उदाहरण इनपुट पर चाहते हैं।

एक और, अधिक संवादात्मक तरीका लाइन-चयन मोड (शिफ्ट-वी) और तीर कुंजी या खोज का उपयोग करके उपयुक्त लाइनों का चयन करना है, और फिर उपयोग करना :!nl । आपके उदाहरण इनपुट के लिए एक पूर्ण कमांड अनुक्रम हो सकता है

/@@inline-code-start
jV/@@inline-code-end
k:!nl

यह स्वचालन के लिए बहुत अनुकूल नहीं है (उदाहरण के लिए सीड का उपयोग करने वाले उत्तर इसके लिए बेहतर हैं), लेकिन एक बार के संपादन के लिए यह 20-पंक्ति गोले का सहारा लेने के लिए बहुत उपयोगी नहीं है।

यदि आप vi (m) से परिचित नहीं हैं, तो आपको कम से कम यह जानना चाहिए कि इन परिवर्तनों के बाद आप फ़ाइल का उपयोग करके सहेज सकते हैं :wq


हाँ, विम कमाल है! लेकिन मैं इस मामले में, एक स्क्रिप्ट योग्य समाधान की तलाश में हूं।
जेम्स स्क्रिपवेन

@ जेम्स, कोई भी, जो कहता है कि विम अपर्याप्त रूप से निर्धारित नहीं है। सबसे पहले एक प्रोजेक्ट डायरेक्टरी बनाएं और उस डायरेक्टरी में अपने होम डायरेक्टरी से vim की सभी स्टार्टअप फाइल्स को कॉपी करें (ln -s काम को छोड़कर केवल .vimrc जिसे हम मॉडिफाई करने वाले हैं और .viminfo जो शोर से भरी हो सकती है)। फ़ंक्शन परिभाषा जोड़ें जो नई .vimrc फ़ाइल में काम करेगी और फिर vim को कॉल करेगी HOME=$(pwd) vim -c 'call Mf()' f। यदि आप xargs का उपयोग कर रहे हैं, तो आप अपने tty को दूषित करने से रखने के लिए एक समर्पित xserver पर gvim का उपयोग करना चाह सकते हैं (vnc वीडियो कार्ड स्वतंत्र है और इसकी निगरानी की जा सकती है)।
हल्दी

@ हिल्ड्रेड हम्म ... क्या मैं सिर्फ वीआईएस क्लिक पर माउस क्लिक करने के लिए [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) का उपयोग नहीं कर सका ?
जेम्स स्क्रिपवेन

2

सबसे आसान हल जो मैं सोच सकता हूं वह है उपयोग न करना nlबल्कि खुद की रेखाओं को गिनना:

#!/usr/bin/env bash
while read line
do
    if [[ $line == @@inline-code-start* ]]
    then
        active=true
    elif [[ $line == @@inline-code-end* ]]
    then
        active=false
    elif [[ $active = true ]]
    then
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

आप इसे फ़ाइल पर चलाते हैं:

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D

धन्यवाद टेरडॉन। मैंने यह स्पष्ट करने के लिए प्रश्न को अपडेट किया कि मैं एक इनपुट के उपधारा को फ़िल्टर करने के लिए एक सामान्य समाधान की तलाश कर रहा हूं, बल्कि नंबरिंग लाइनों का विशिष्ट उदाहरण। शायद एक बेहतर उदाहरण कमांड "टैक" (रिवर्स लाइन्स) रहा होगा
जेम्स स्क्रिपवेन

2

यदि आपका लक्ष्य एक ही प्रक्रिया उदाहरण में पूरे कोड ब्लॉक को भेजना है, तो आप लाइनों को जमा कर सकते हैं और पाइपिंग में देरी कर सकते हैं जब तक कि आप कोड ब्लॉक के अंत तक नहीं पहुंचते:

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

यह एक इनपुट फ़ाइल के लिए निम्नलिखित का उत्पादन करता है जो तीन बार परीक्षण मामले को दोहराता है:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D

कोड ब्लॉक के साथ कुछ और करने के लिए, जैसे रिवर्स और फिर नंबर, बस इसे कुछ और के माध्यम से पाइप करें: echo -E "${acc:1}" | tac | nl :। परिणाम:

line A
line B
     1  line Z
     2  line Y
     3  line X
line C
line D

या वर्डकाउंट echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D

2

उपयोगकर्ता द्वारा प्रदान किए गए फ़िल्टर को परिभाषित करने के लिए संपादन ने एक विकल्प जोड़ा

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

डिफ़ॉल्ट रूप से फ़िल्टर "nl" है। कुछ उपयोगकर्ता द्वारा दिए गए कमांड के साथ फ़िल्टर उपयोग विकल्प "-p" बदलने के लिए:

codify -p="wc" file

या

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

यह अंतिम फ़िल्टर आउटपुट देगा:

line A
line B
 ╓─
  line X
  line Y
  line Z
 ╙─
line C
line D

अपडेट १ IPC का उपयोग :: Open2 में स्केलिंग की समस्या है: यदि बफ़र्स को पार किया जाता है तो यह ब्लॉक हो सकता है। (मेरी मशीन में पाइप बफ़र करता है अगर 64K 10_000 x "लाइन Y" के अनुरूप है)।

अगर हमें बड़ी चीजों की आवश्यकता है (क्या हमें 10000 "लाइन वाई" की आवश्यकता है):

(1) स्थापित करें और उपयोग करें use Forks::Super 'open2';

(2) या फंक्शन पाइपिट को इसके द्वारा प्रतिस्थापित करें:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}

यह वास्तव में अच्छा है। मुझे लगता है कि ट्रिक्स यह है कि आप लाइन को लाइन (रिडिफाइनिंग $/और sफ्लैग द्वारा) प्रोसेस नहीं कर रहे हैं , और eबाहरी कमांड को वास्तविक कॉल करने के लिए फ्लैग का उपयोग करते हैं। मैं वास्तव में दूसरी (एससीआई कला) उदाहरण पसंद करता हूं!
जेम्स स्क्रीवेन

मुझे लगता है मैं हालांकि देखा है, यह है कि यह उपधारा में एक हजार लाइनों से परे पैमाने पर नहीं लगता है। मुझे संदेह है कि यह उपधारा को पाठ के एक बड़े ब्लॉक के रूप में मानने के साथ है।
जेम्स स्क्रीवेन

धन्यवाद। हां: `/ e` = eval; /s= ("।" का अर्थ है (.|\n)); $/रिडिफाइन रजिस्टर सेपरेटर।
JJoao

@ नाम, आप सही हैं (पाइप अवरुद्ध है)। मुझे परखने दीजिए कि क्या हो रहा है ...
जजॉओ

@JamesScriven, कृपया मेरा अपडेट देखें ...
JJoao

1

यह जाग के लिए एक काम है।

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

जब स्क्रिप्ट स्टार्ट मार्कर देखती है, तो यह नोट करती है कि उसे पाइपिंग शुरू करनी चाहिए nl। जब pipeचर सत्य (नॉनज़रो) होता है, तो आउटपुट को nlकमांड में पाइप किया जाता है ; जब चर गलत है (परेशान या शून्य), तो आउटपुट सीधे मुद्रित होता है। पाइप कमांड को पहली बार फोर्क किया जाता है जब पाइप कमांड प्रत्येक कमांड स्ट्रिंग के लिए मिलता है। उसी स्ट्रिंग के साथ पाइप ऑपरेटर के बाद के मूल्यांकन मौजूदा पाइप का पुन: उपयोग करते हैं; एक अलग स्ट्रिंग मान एक अलग पाइप बनाएगा। closeसमारोह को देखते हुए कमांड स्ट्रिंग के लिए पाइप बंद कर देता है।


यह अनिवार्य रूप से एक नामित पाइप का उपयोग करके आपके शेल स्क्रिप्ट के समान तर्क है, लेकिन वर्तनी को बहुत आसान है, और करीबी तर्क सही है। आपको सही समय पर पाइप को बंद करने की आवश्यकता है, ताकि nlकमांड से बाहर निकल कर, उसके बफ़र को फ्लश किया जा सके। आपकी स्क्रिप्ट वास्तव में पाइप को बहुत जल्दी बंद कर देती है: जैसे ही echo $line >myfifoनिष्पादन समाप्त होता है, पाइप बंद हो जाता है । हालाँकि, nlकमांड केवल फ़ाइल के अंत को देखती है अगर उसे स्क्रिप्ट निष्पादित होने से पहले समय स्लाइस मिलता है echo $line >myfifo। यदि आपके पास बड़ी मात्रा में डेटा था, या यदि आप sleep 1लिखने के बाद जोड़ते हैं myfifo, तो आप देखेंगेnl केवल पहली पंक्ति या पहली त्वरित पंक्तियों की प्रक्रिया करता है, फिर यह बाहर निकलता है क्योंकि इसके इनपुट के अंत को देखा जाता है।

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

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

(मैंने सही उद्धरण जोड़ने का अवसर भी लिया और इस तरह - देखें कि मेरी शेल स्क्रिप्ट व्हॉट्सएप या अन्य पात्रों पर क्यों चोक करती है? )

यदि आप ऐसा कर रहे हैं, तो आप नामांकित पाइप के बजाय पाइप लाइन का उपयोग कर सकते हैं।

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done

आपका अजीब समाधान वास्तव में अच्छा है! मुझे लगता है कि अब तक का सबसे संक्षिप्त (बहुत पठनीय) समाधान है। क्या वाइक गारंटी के लिए पाइप का पुन: उपयोग करने के बारे में जागृत व्यवहार है, या यह फैसला कर सकता है, "अरे, आपने अभी के लिए पर्याप्त पाइप किया है..मैं इस पाइप को बंद करने और एक नया खोलने जा रहा हूं"। आपका "पाइपलाइन" समाधान भी वास्तव में अच्छा है। मैंने छोरों के साथ एम्बेडेड होने के साथ मौखिक रूप से छूट दी, जैसा कि मैंने सोचा कि यह थोड़ा भ्रमित हो सकता है, लेकिन मुझे लगता है कि आपके पास बहुत अच्छा है। इससे पहले एक अर्धविराम गायब है do। (मेरे पास एक छोटा सा संपादन करने के लिए यहाँ
रिपीट नहीं है

1
... मैं आपका नाम पाइप समाधान काम नहीं कर सका। एक दौड़ की स्थिति प्रतीत होती है, जैसे कि सेक्शन विद विंक कभी-कभी पूरी तरह से खो जाता है। इसके अलावा, अगर कोई दूसरा @@ इनलाइन-कोड-स्टार्ट / एंड सेक्शन है, तो यह हमेशा खो जाता है।
जेम्स स्क्रिपवेन

0

ठीक है, पहले बंद; मैं समझता हूं कि आप अपनी फ़ाइल के अनुभागों में पंक्तियों को क्रमांकित करने का तरीका नहीं खोज रहे हैं। चूंकि आपने कोई वास्तविक उदाहरण नहीं दिया है कि आपका फ़िल्टर (इसके अलावा nl) क्या हो सकता है , तो मान लीजिए कि यह है

tr "[[:lower:]]" "[[:upper:]]"

अर्थात, पाठ को सभी ऊपरी मामलों में रूपांतरित करें; इसलिए, इनपुट के लिए

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

आप का आउटपुट चाहते हैं

line A
line B
LINE X
LINE Y
LINE Z
line C
line D

यहाँ एक समाधान का मेरा पहला सन्निकटन है:

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

जहां @@स्ट्रिंग्स से पहले रिक्त स्थान , और अंतिम पंक्ति के अंत के पास, टैब हैं। कृपया ध्यान दें कि मैं उपयोग कर रहा हूंnl अपने उद्देश्यों के लिए। (बेशक मैं इसे आपकी समस्याको हल करने के लिए कर रहा हूं, लेकिन आपको लाइन-क्रमांकित आउटपुट देने के लिए नहीं।)

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

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

फिर हम दौड़ते हैं file1(जो सभी का संघटन है कैपिटलाइज़ेशन फ़िल्टर के माध्यम से इन-सेक्शन लाइनों का संघटन है) ; संयुक्त आउट-ऑफ-सेक्शन लाइनों के साथ संयोजन करें; क्रमबद्ध करें, उन्हें उनके मूल क्रम में वापस लाने के लिए; और फिर लाइन संख्याओं को छीन लें। यह मेरे उत्तर के शीर्ष के पास दिखाए गए आउटपुट का उत्पादन करता है।

यह मानता है कि आपका फ़िल्टर लाइन संख्या को अकेला छोड़ देता है। यदि यह नहीं है (उदाहरण के लिए, यदि यह पंक्ति की शुरुआत में वर्ण सम्मिलित करता है या हटाता है), तो, मेरा मानना ​​है कि यह सामान्य दृष्टिकोण अभी भी उपयोग किया जा सकता है, लेकिन इसके लिए थोड़े पेचीदा कोडिंग की आवश्यकता होगी।


nlवहां पहले से ही अधिकांश काम करता है - यही इसका -dएलिमिटर विकल्प है।
मोकेसर

0

एक शेल स्क्रिप्ट जो गैर-सीमांकित लाइनों के आउटपुट विखंडू में सेड का उपयोग करती है और एक फिल्टर में लाइनों के सीमांकित टुकड़े को फ़ीड करती है:

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

मैं एक फ़ाइल का नाम detagger.sh में इस पटकथा लिखी है और यह के रूप में तो प्रयोग किया है: ./detagger.sh infile.txt। मैंने प्रश्न में फ़िल्टरिंग कार्यक्षमता की नकल करने के लिए एक अलग filter.sh फ़ाइल बनाई:

#!/bin/bash
awk '{ print "\t" NR " " $0}'

लेकिन फ़िल्टरिंग ऑपरेशन को कोड में बदला जा सकता है।

मैंने इसके साथ एक सामान्य समाधान के विचार का पालन करने का प्रयास किया ताकि नंबरिंग लाइनों जैसे संचालन को अतिरिक्त / आंतरिक गिनती की आवश्यकता न हो। स्क्रिप्ट कुछ अल्पविकसित जाँच करती है यह देखने के लिए कि सीमांकन टैग जोड़े में हैं और नेस्टेड टैग को इनायत से नहीं संभालता है।


-1

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

मेरा समाधान:

(मैं nlएक उदाहरण फिल्टर के रूप में उपयोग करता हूं )

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

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


मैं, सोचा था कि आप "लाइनों भर में संचित राज्य" करने में सक्षम होना चाहता था, इसलिए उदाहरण के लिए माइक के परीक्षण डाटा, लाइनों का उपयोग कर M, Nऔर Oगिने किया जाएगा 4, 5और 6। यह ऐसा नहीं करता है। मेरा जवाब है (इस तथ्य से अलग कि, अपने वर्तमान अवतार में, यह nlफिल्टर के रूप में काम नहीं करता है )। यदि यह उत्तर आपको वह आउटपुट दे रहा है जो आप चाहते हैं, तो "लाइनों के माध्यम से राज्य जमा करें" से आपका क्या मतलब है? क्या आपका मतलब यह था कि आप केवल प्रत्येक अनुभाग के माध्यम से राज्य को संरक्षित करना चाहते थे , लेकिन (पार) वर्गों के बीच नहीं ? (आपने अपने प्रश्न में एक बहु-खंड उदाहरण क्यों नहीं दिया?)
स्कॉट

@ सेट - nl -pप्राप्त करने के लिए उपयोग करें M,N,O==4,5,6
mikeserv

मैंने यह स्पष्ट करने के लिए प्रश्न को अद्यतन किया कि मैं केवल उपधारा के भीतर राज्य बनाए रखने में दिलचस्पी रखता हूं, हालांकि मुझे लगता है कि अन्य व्याख्या समान रूप से दिलचस्प है।
जेम्स स्क्रिपवेन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.