กรองหรือไพพ์บางส่วนของไฟล์


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เป็นตัวอย่างในการทำให้คำถามง่ายขึ้นในความเป็นจริงคำสั่งเป็นสคริปต์ที่กำหนดเองที่ซับซ้อนมากขึ้นสิ่งที่ฉันกำลังมองหา for เป็นคำตอบทั่วไปสำหรับปัญหาในการใช้ตัวกรองมาตรฐานกับส่วนย่อยของอินพุตไฟล์ )

ผลลัพธ์ควรมีลักษณะดังนี้:

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

แต่ฉันคิดว่าการตีความปัญหาเนื่องจากต้องการให้รัฐเก็บข้ามส่วนต่างๆนั้นถูกต้องและมีประโยชน์ในหลายบริบท

จบการอัพเดต 2

ความคิดแรกของฉันคือการสร้างเครื่องสถานะเรียบง่ายที่ติดตามส่วนที่เราอยู่:

#!/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

ความพยายามครั้งต่อไปของฉันคือการใช้ Fifo:

#!/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's คั่นส่วน
mikeserv

nl เป็นเพียงตัวอย่าง ในกรณีของฉันฉันใช้สคริปต์ที่กำหนดเองมากกว่า nl
James Scriven

ในกรณีนี้โปรดอธิบายสิ่งที่สคริปต์ของคุณกำลังทำอยู่
terdon

ฉันชี้แจงในคำถามที่ฉันใช้nlเป็นตัวกรองตัวอย่างเท่านั้น ฉันคิดว่ามันจะทำให้คำถามง่ายขึ้นโดยการคัดรายละเอียดของสิ่งที่ตัวกรองทำ แต่ฉันอาจทำให้เกิดความสับสนมากขึ้น ในความเป็นจริงฉันกำลังกรองส่วนย่อยผ่านปากกาเน้นข้อความเพื่อสร้างตัวสร้างบล็อกแบบคงที่ในบ้าน ตอนนี้ฉันกำลังใช้ gnu source-highlightแต่นั่นอาจเปลี่ยนไปและฉันอาจเพิ่มตัวกรองเพิ่มเติมเช่นตัวจัดรูปแบบเช่นกัน
James Scriven

คำตอบ:


7

ฉันจะเห็นด้วยกับคุณ - มันอาจเป็นปัญหาทั่วไป สาธารณูปโภคส่วนกลางบางแห่งมีสิ่งอำนวยความสะดวกสำหรับการจัดการ


nl

nlยกตัวอย่างเช่นแยกเข้าสู่หน้าตรรกะเป็น-delimited โดยทั้งสองตัวละครส่วนคั่น สามเกิดขึ้นบนเส้นคนเดียวทั้งหมดบ่งชี้ถึงการเริ่มต้นของการที่หัว , สองร่างกายและหนึ่งส่วนท้ายส่วนท้ายมันจะแทนที่สิ่งเหล่านี้ที่พบในอินพุตด้วยบรรทัดว่างในเอาต์พุต - ซึ่งเป็นบรรทัดว่างเท่านั้นที่เคยพิมพ์

./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การส่งออกอาจบิดเบือนตามค่าเริ่มต้น-delimiter \:ผมได้เรียนรู้ที่จะต้องระมัดระวังมากขึ้นกับมันและเริ่มใช้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นับ-sidth + eparator len) * ช่องว่างที่ส่วนหัวของบรรทัดที่ไม่มีหมายเลข สิ่งนี้ช่วยให้คุณสามารถทำซ้ำเนื้อหาที่ไม่ได้หมายเลขโดยเปรียบเทียบกับเนื้อหาที่มีหมายเลข - และด้วยความพยายามเพียงเล็กน้อย เมื่อคุณพิจารณาว่าnlจะแบ่งอินพุตออกเป็นส่วนตรรกะสำหรับคุณและคุณสามารถแทรก-strings ตามอำเภอใจที่หัวของแต่ละบรรทัดมันเป็นตัวเลขแล้วมันจะค่อนข้างง่ายในการจัดการเอาต์พุต:

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

GNU sed

หากnlไม่ใช่แอปพลิเคชันเป้าหมายของคุณ GNU sedสามารถexecute คำสั่งเชลล์ตามอำเภอใจสำหรับคุณโดยขึ้นอยู่กับคู่ที่ตรงกัน

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

ด้านบนsedรวบรวมอินพุตในพื้นที่รูปแบบจนกว่ามันจะเพียงพอที่จะผ่านการทดแทนได้สำเร็จTและหยุดการbranching กลับไปที่อา:lเบล เมื่อเป็นเช่นนั้นมันจะexecutes nlพร้อมอินพุตที่แสดงเป็น<<here-document สำหรับส่วนที่เหลือทั้งหมดของ pattern-space

เวิร์กโฟลว์เป็นดังนี้:

  1. /^@@.*start$/!b
    • ถ้า^ทั้งเส้น$ไม่!ไม่/ตรงกับ/รูปแบบดังกล่าวข้างต้นแล้วมันจะbranched จากสคริปต์และ autoprinted - ดังนั้นจากจุดนี้เราเป็นเพียงการทำงานร่วมกับชุดของเส้นที่เริ่มต้นด้วยรูปแบบที่
  2. s//nl <<\\@@/
    • ที่ว่างเปล่าs//ฟิลด์/ยืนอยู่ในสำหรับที่อยู่ที่ผ่านมาsedพยายามที่จะตรงกับ - เพื่อให้คำสั่งนี้ทดแทนทั้ง@@.*startสายnl <<\\@@แทน
  3. :l;N
    • :คำสั่งกำหนดฉลากสาขา - ที่นี่ฉันจะตั้งคนหนึ่งชื่อ:lอาเบล Nคำสั่งต่อผนวกบรรทัดถัดไปของการป้อนข้อมูลไปยังพื้นที่รูปแบบตามด้วย\nตัวอักษร ewline นี่เป็นเพียงหนึ่งในไม่กี่วิธีในการรับ\newline ในsedพื้นที่รูปแบบ - \nอักขระ ewline นั้นเป็นตัวคั่นที่แน่นอนสำหรับsedผู้ที่เคยทำมันมาชั่วขณะ
  4. s/\(\n@@\)[^\n]*end$/\1/
    • s///ubstitution นี้สามารถประสบความสำเร็จได้หลังจากพบการเริ่มต้นและเกิดขึ้นครั้งแรกหลังจากสิ้นสุดบรรทัด มันจะกระทำเฉพาะในพื้นที่รูปแบบที่\newline สุดท้ายตามมาทันทีโดยการ@@.*endทำเครื่องหมายที่ส่วนท้ายสุด$ของพื้นที่รูปแบบ เมื่อมันไม่กระทำจะแทนที่สตริงจับคู่ทั้งกับ\1ครั้งแรกของ\(กลุ่มหรือ\)\n@@
  5. Tl
    • Tสาขาคำสั่งคือป้ายกำกับ(หากมี)หากมีการเปลี่ยนตัวที่ประสบความสำเร็จไม่ได้เกิดขึ้นตั้งแต่ครั้งสุดท้ายที่สายการป้อนข้อมูลที่ถูกดึงเข้าไปในพื้นที่รูปแบบ(ที่ผมทำ w /N ) ซึ่งหมายความว่าทุกครั้งที่\newline ต่อท้ายพื้นที่รูปแบบซึ่งไม่ตรงกับตัวคั่นปลายของคุณTคำสั่ง est จะล้มเหลวและแยกกลับไปที่อา:lเบลซึ่งส่งผลให้เกิดการsedดึงในส่วนNต่อขยายและวนซ้ำ
  6. e

    • เมื่อทดแทนสำหรับการแข่งขันที่ได้คือประสบความสำเร็จและสคริปต์ที่ไม่ได้กลับสาขาล้มเหลวTคือ, sedจะexecute คำสั่งที่looks เช่นนี้

      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ดึงเข้าแถว หากประสบความสำเร็จหมายความว่าลูปยังไม่ได้กด EOF ดังนั้นในการcaseจับคู่กับตัวคั่นเริ่มต้นdoบล็อกจะถูกดำเนินการทันที อื่นprintfพิมพ์$lineมันreadและsedเรียกว่า

sedจะprint ทุกบรรทัดจนกว่าจะพบเครื่องหมายเริ่มต้น - เมื่อquits อินพุตทั้งหมด -uสวิตช์ nbuffered เป็นสิ่งจำเป็นสำหรับ GNU sedเพราะมันสามารถ buffer ค่อนข้างตะกละตะกลามอย่างอื่น แต่ - ตามสเปค - อื่น ๆ 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

นี่เป็นเพียงตัวอย่าง - สิ่งใดก็ตามที่สามารถทำได้ในการทดสอบหรือบล็อกที่นี่ แต่ยูทิลิตี้แรกจะต้องไม่ใช้อินพุตมากเกินไป

สาธารณูปโภคทั้งหมดที่เกี่ยวข้องอ่านข้อมูลเดียวกัน - และพิมพ์ผลลัพธ์ของพวกเขา - ในทางกลับกัน ชนิดของสิ่งนี้อาจเป็นเรื่องยากที่จะได้รับการแขวนของ - เพราะสาธารณูปโภคที่แตกต่างกันจะ buffer มากกว่าคนอื่น ๆ - แต่คุณโดยทั่วไปสามารถพึ่งพาdd, headและsedที่จะทำสิ่งที่ถูกต้อง( แต่สำหรับ GNU sedคุณต้อง CLI สวิทช์)และ คุณควรจะสามารถที่จะพึ่งพาread- เพราะมันเป็นไปตามธรรมชาติช้ามาก และนั่นคือสาเหตุที่ลูปข้างต้นเรียกมันว่าเพียงครั้งเดียวต่อบล็อกอินพุต


ฉันทดสอบsedตัวอย่างที่สองที่คุณให้และมันใช้งานได้ แต่ฉันมีปัญหาในการอ่านไวยากรณ์ (ความอ่อนแอของฉันค่อนข้างอ่อนแอและมัก จำกัด อยู่ที่ s / findthis / replacethis / g ฉันจะต้องใช้ความพยายามที่จะนั่งลงและเข้าใจความจริง ๆ )
James Scriven

@ JamesScriven - ฉันเพิ่งแก้ไขเพื่ออธิบายได้ดีขึ้น แจ้งให้เราทราบหากไม่ช่วย ฉันเปลี่ยนคำสั่งเป็นจำนวนมาก - ตอนนี้ชิ้นเล็กลงและมีเหตุผลมากขึ้น
mikeserv

4

ความเป็นไปได้อย่างหนึ่งคือการทำเช่นนี้กับโปรแกรมแก้ไขข้อความเป็นกลุ่ม มันสามารถไปป์ส่วนใดก็ได้ผ่านคำสั่งเชลล์

:4,6!nlวิธีหนึ่งที่จะทำเช่นนี้คือหมายเลขบรรทัดโดยใช้ คำสั่ง ex นี้จะรัน nl บนบรรทัด 4-6 ที่รวมกันบรรลุสิ่งที่คุณต้องการในอินพุตตัวอย่างของคุณ

อีกวิธีการโต้ตอบมากขึ้นคือการเลือกสายที่เหมาะสมโดยใช้โหมดบรรทัดเลือก (Shift-V) :!nlและปุ่มลูกศรหรือค้นหาและจากนั้นใช้ ลำดับคำสั่งแบบเต็มสำหรับอินพุตตัวอย่างของคุณอาจเป็นได้

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

สิ่งนี้ไม่เหมาะกับระบบอัตโนมัติ (คำตอบที่ใช้เช่น sed ดีกว่า) แต่สำหรับการแก้ไขแบบครั้งเดียวมันมีประโยชน์มากโดยไม่ต้องหันไปใช้ shellscripts 20 บรรทัด

หากคุณไม่คุ้นเคยกับ vi (m) :wqคุณควรที่รู้น้อยมากว่าหลังจากการเปลี่ยนแปลงเหล่านี้คุณสามารถบันทึกไฟล์โดยใช้


ใช่เป็นกลุ่มที่น่ากลัว! แต่ในกรณีนี้ฉันกำลังมองหาโซลูชันที่ใช้สคริปต์ได้
James Scriven

@ JamesScriven ทุกคนที่กล่าวว่าเป็นกลุ่มไม่สามารถเขียนสคริปต์ในการพิจารณาไม่เพียงพอ เริ่มแรกสร้างไดเรกทอรีโครงการและในไดเรกทอรีนั้นคัดลอกไฟล์เริ่มต้นทั้งหมดของ vim จากโฮมไดเร็กตอรี่ของคุณ (ln -s ทำงานได้ดียกเว้น. vimrc ที่เรากำลังจะแก้ไขและ. viminfo ซึ่งอาจเต็มไปด้วยเสียง) เพิ่มฟังก์ชั่นความหมายที่จะทำผลงานไปยังแฟ้ม .vimrc HOME=$(pwd) vim -c 'call Mf()' fใหม่แล้วเรียกเป็นกลุ่มเป็น หากคุณใช้ xargs คุณอาจต้องการใช้ gvim บน xserver เฉพาะเพื่อป้องกันไม่ให้ tty ของคุณเสียหาย (vnc เป็นการ์ดแสดงผลอิสระและสามารถตรวจสอบได้)
hildred

@hildred อืม ... ฉันไม่สามารถใช้ [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) เพื่อจำลองการคลิกเมาส์เพื่อเป็นกลุ่มได้หรือไม่
James Scriven

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

ขอบคุณ terdon ฉันอัปเดตคำถามเพื่อให้ความกระจ่างว่าฉันกำลังมองหาโซลูชันทั่วไปเพื่อกรองส่วนย่อยของอินพุตแทนที่จะเป็นตัวอย่างเฉพาะของหมายเลขบรรทัด บางทีอาจจะเป็นคำสั่งตัวอย่างที่ดีที่จะได้รับ "แทค" (เส้นย้อนกลับ)
เจมส์ Scriven

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

อัปเดต 1 การใช้ IPC :: Open2 มีปัญหาเกี่ยวกับการปรับสเกล: ถ้าเกินบัฟเฟอร์มันอาจบล็อก (ในเครื่องของฉันบัฟเฟอร์ไปป์ถ้า 64K ตรงกับ 10_000 x "line Y")

หากเราต้องการสิ่งที่ใหญ่กว่า (เราต้องการมากกว่า 10,000 "line Y"):

(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;
}

มันเจ๋งจริงๆ ฉันเดาว่าคุณไม่ได้ประมวลผลทีละบรรทัด (โดยการ redifining $/และsค่าสถานะ) และการใช้eค่าสถานะเพื่อทำการโทรจริงไปยังคำสั่งภายนอก ฉันชอบตัวอย่างที่สอง (ศิลปะ ASCII) จริง ๆ !
James Scriven

สิ่งที่ฉันสังเกตเห็นคือสิ่งนี้ดูเหมือนจะไม่ขยายเกินสองพันบรรทัดในส่วนย่อย ฉันสงสัยว่าสิ่งนี้เกี่ยวข้องกับการรักษาส่วนย่อยให้เป็นข้อความขนาดใหญ่หนึ่งบล็อก
James Scriven

ขอบคุณ ใช่: `/ e` = eval; /s= ("." หมายถึง(.|\n)); $/กำหนดตัวแยกรีจิสเตอร์ใหม่
JJoao

@ JamesScriven คุณพูดถูก (ท่อกำลังปิดกั้น) ให้ฉันทดสอบสิ่งที่เกิดขึ้น ...
JJoao

@ JamesScriven โปรดดูการอัปเดตของฉัน ...
JJoao

1

นั่นเป็นงานสำหรับ awk

#!/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คำสั่ง เมื่อตัวแปรเป็นเท็จ (ไม่ได้ตั้งค่าหรือเป็นศูนย์) เอาต์พุตจะถูกพิมพ์โดยตรง คำสั่ง piped ถูกแยกเป็นครั้งแรกที่พบว่ามีการสร้างไพพ์สำหรับแต่ละสตริงคำสั่ง การประเมินผลที่ตามมาของผู้ประกอบการไปป์ด้วยสายอักขระเดียวกันจะใช้ซ้ำไพพ์ที่มีอยู่เดิม ค่าสตริงที่แตกต่างกันจะสร้างไปป์ที่แตกต่างกัน 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

ทางออก awk ของคุณดีจริงๆ! ฉันคิดว่านี่เป็นทางออกที่กระชับที่สุด แต่อ่านง่ายมาก พฤติกรรมของ awk คือการนำท่อกลับมาใช้เพื่อรับประกัน nl หรือไม่สามารถตัดสินใจได้ว่า "เดี๋ยวก่อนคุณได้ประปาเพียงพอแล้วตอนนี้ฉันจะปิดท่อนี้แล้วเปิดท่อใหม่" หรือไม่? โซลูชัน "ไปป์ไลน์" ของคุณนั้นยอดเยี่ยมเช่นกัน ฉันลดวิธีการฝังตัวลงในขณะที่ลูปเพราะฉันคิดว่ามันอาจจะสับสนเล็กน้อย แต่ฉันคิดว่าสิ่งที่คุณมีนั้นยอดเยี่ยม doมีอัฒภาคหายไปก่อนที่จะเป็น (ฉันไม่มีตัวแทนที่นี่เพื่อทำการแก้ไขเล็กน้อย)
James Scriven

1
... ฉันแก้ปัญหาไปป์ที่มีชื่อของคุณไม่ได้ ดูเหมือนว่าจะมีสภาพการแข่งขันเช่นนั้นส่วนที่ส่งไปยัง nl บางครั้งอาจสูญหายได้ทั้งหมด นอกจากนี้ถ้ามีส่วน @@ inline-code-start / end ส่วนที่สองจะหายไปเสมอ
James Scriven

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ตัวเลือกตัวคัดใช้สำหรับ
mikeserv

0

เชลล์สคริปต์ที่ใช้ sed เพื่อเอาต์พุต chunks ของบรรทัดที่ไม่ได้แบ่งเขตและฟีด demarcated chunks ของบรรทัดในโปรแกรมตัวกรอง:

#!/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

ขอบคุณสำหรับความคิดที่ยอดเยี่ยมทั้งหมด ฉันคิดวิธีแก้ปัญหาของตัวเองโดยการติดตามส่วนย่อยในไฟล์ temp และวางท่อทั้งหมดในคราวเดียวกับคำสั่งภายนอกของฉัน สิ่งนี้คล้ายกับสิ่งที่ Supr แนะนำ (แต่มีตัวแปรเชลล์แทนไฟล์ temp) นอกจากนี้ฉันชอบความคิดในการใช้ 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

ฉันไม่ต้องการจัดการกับไฟล์ temp แต่ฉันเข้าใจว่าตัวแปร shell สามารถมีขนาด จำกัด ค่อนข้างต่ำและฉันไม่รู้โครงสร้าง bash ใด ๆ ที่ทำงานเหมือนไฟล์ temp แต่หายไปโดยอัตโนมัติเมื่อ กระบวนการสิ้นสุดลง


ฉันคิดว่าคุณต้องการที่จะสามารถที่จะ“รัฐสะสมข้ามเส้น” ดังนั้นสำหรับตัวอย่างเช่นการใช้ข้อมูลการทดสอบของ mike เส้นM, NและOจะได้รับหมายเลข4, และ5 6นี่ไม่ได้ทำอย่างนั้น คำตอบของฉันคือ (นอกเหนือจากข้อเท็จจริงที่ว่าในการจุติมาใหม่ในปัจจุบันมันไม่ได้ทำงานnlเป็นตัวกรอง) ถ้านี้คำตอบคือให้คุณเอาท์พุทที่คุณต้องการแล้วสิ่งที่คุณไม่ได้หมายถึง“รัฐสะสมข้ามเส้น”? คุณไม่ได้หมายความว่าคุณต้องการที่จะรักษารัฐเดียวผ่านแต่ละส่วน แต่ไม่ได้ระหว่าง (ตรงข้าม) ส่วน? (ทำไมคุณไม่ใส่ตัวอย่างหลายส่วนในคำถามของคุณ?)
Scott

@Scott - การใช้งานที่จะได้รับnl -p M,N,O==4,5,6
mikeserv

ฉันอัปเดตคำถามเพื่อให้ความกระจ่างว่าฉันเพียง แต่สนใจในการรักษาสถานะไว้ในส่วนย่อยแม้ว่าฉันคิดว่าการตีความอื่น ๆ นั้นน่าสนใจไม่แพ้กัน
James Scriven
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.