ลบช่วงของบรรทัดด้านบนรูปแบบด้วย sed (หรือ awk)


28

ฉันมีรหัสต่อไปนี้ที่จะลบบรรทัดที่มีรูปแบบbananaและ 2 บรรทัดหลังจากนั้น:

sed '/banana/I,+2 d' file

จนถึงตอนนี้ดีมาก! แต่ฉันต้องการมันเพื่อลบ 2 บรรทัดก่อนหน้า bananaแต่ฉันไม่สามารถรับมันด้วย "เครื่องหมายลบ" หรืออะไรก็ตาม (คล้ายกับสิ่งที่grep -v -B2 banana fileควรทำ แต่ไม่ได้ทำ):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'

1
awk '{l[m=NR]=$0}/banana/{for(i=NR-2;i<=NR;i++)delete l[i]}END{for(i=1;i<=m;i++)if(i in l)print l[i]}'ที่ง่ายที่สุดคือการโหลดข้อมูลทั้งหมดเป็นอาร์เรย์ข้ามเส้นที่ไม่พึงประสงค์แล้วออกสิ่งที่เหลืออยู่: มันไม่มีประสิทธิภาพดังนั้นนี่เป็นเพียงคำใบ้ไม่ใช่วิธีแก้ปัญหา
จัดการ

6
tac file | sed ... | tacเพียงแค่ทำ : P
กัส

@angus ฉันไม่ได้คิดเกี่ยวกับมัน;)
Teresa e Junior

1
คุณสามารถทำสิ่งsed '/banana/,+2d' file นั้นได้ซึ่งจะใช้ได้
Akaks

1
หากคุณเปิดการใช้ awk มันค่อนข้างง่าย: awk 'tolower($0)~/bandana/{print prev[!idx];print prev[idx]} {idx=!idx;prev[idx]=$0}' filein เนื่องจากนี่เป็นความคิดเห็นและไม่ใช่คำตอบ (มีคำตอบอื่น ๆ อยู่แล้ว) ฉันจะไม่พูดถึงรายละเอียดมากนัก แต่สิ่งที่สำคัญที่สุดคือคุณมี ก่อนหน้านี้สองระเบียนในก่อนหน้า [0] และก่อนหน้า [1] ที่ "สดใหม่" ซึ่งขึ้นอยู่กับการย้ำเสมอ แต่ในprev[idx]ดังนั้นเมื่อคุณพิมพ์คุณพิมพ์ใน!idxแล้วidxการสั่งซื้อ โดยไม่คำนึงถึงทางเลือกและใส่ระเบียนปัจจุบันในidx prev[idx]
Luv2code

คำตอบ:


22

Sed ไม่ได้ย้อนรอย: เมื่อมีการประมวลผลบรรทัดมันก็เสร็จแล้ว ดังนั้น "ค้นหาบรรทัดและพิมพ์บรรทัด N ก่อนหน้า" จะไม่ทำงานอย่างที่เป็นอยู่ซึ่งแตกต่างจาก "ค้นหาบรรทัดและพิมพ์บรรทัด N ถัดไป" ซึ่งง่ายต่อการต่อกิ่ง

หากไฟล์ไม่ยาวเกินไปเนื่องจากคุณดูเหมือนจะใช้ได้กับส่วนขยาย GNU คุณสามารถใช้tacเพื่อย้อนกลับบรรทัดของไฟล์

tac | sed '/banana/I,+2 d' | tac

อีกมุมหนึ่งของการโจมตีคือการบำรุงรักษาหน้าต่างบานเลื่อนในเครื่องมือเช่น awk การปรับจากนั้นมีทางเลือกอื่นสำหรับสวิตช์ -A -B -C grep ของ (เพื่อพิมพ์สองสามบรรทัดก่อนและหลัง) หรือไม่? (คำเตือน: ทดสอบน้อยที่สุด):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

การใช้งาน: /path/to/script -v pattern='banana' -v before=2


2
sedสามารถทำหน้าต่างบานเลื่อนเกินไป awkแต่สคริปต์ที่เกิดขึ้นเป็นปกติอ่านไม่ได้เพื่อให้ง่ายต่อการใช้งานเพียงแค่
jw013

@Gilles .. awkสคริปต์ไม่ถูกต้องนัก ตามที่มันพิมพ์บรรทัดว่างและคิดถึงบรรทัดสุดท้าย ดูเหมือนว่าจะแก้ไขได้ แต่อาจไม่เหมาะหรือถูกต้อง: if (NR-before in h) { print...; delete...; }... และในENDส่วน: for (i in h) print h[i]... นอกจากนี้สคริปต์ awk จะพิมพ์บรรทัดที่ตรงกัน แต่tac/secเวอร์ชันไม่ได้ แต่คำถามนั้นค่อนข้างคลุมเครือในเรื่องนี้ .. สคริปต์ "ต้นฉบับ" awk ซึ่งคุณให้ลิงค์ทำงานได้ดี .. ฉันชอบมัน ... ฉันไม่แน่ใจว่า 'mod' ด้านบนมีผลต่อการพิมพ์อย่างไรบรรทัด ...
Peter.O

@ Peter.O ขอบคุณสคริปต์ awk น่าจะดีกว่าตอนนี้ และฉันใช้เวลาน้อยกว่า 6-8 ปี!
Gilles 'หยุดความชั่วร้าย'

19

นี่เป็นเรื่องง่าย ๆ สำหรับexหรือvim -e

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

นิพจน์อ่าน: สำหรับทุกบรรทัดที่มีกล้วยในช่วงจากบรรทัดปัจจุบัน -2 ถึงบรรทัดปัจจุบันให้ลบ

สิ่งที่เจ๋งคือช่วงนี้ยังมีการค้นหาไปข้างหน้าและข้างหลังเช่นนี้จะลบทุกส่วนของไฟล์ที่เริ่มต้นด้วยบรรทัดที่มีแอปเปิ้ลและลงท้ายด้วยบรรทัดที่มีสีส้มและมีบรรทัดที่มีกล้วย:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

7

ใช้ "หน้าต่างบานเลื่อน" ในperl:

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'

6

คุณสามารถทำได้ง่ายๆด้วยsed:

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

ฉันไม่รู้ว่าทำไมใคร ๆ จะพูดเป็นอย่างอื่น แต่การหาบรรทัดและพิมพ์บรรทัดก่อนหน้านั้น sedรวมเอาPrint primitive ในตัวซึ่งเขียนเฉพาะ\nอักขระ ewline แรกในพื้นที่รูปแบบเท่านั้น Dดั้งเดิม elete เสริมลบส่วนที่เหมือนกันของพื้นที่รูปแบบก่อนที่จะรีไซเคิลสคริปต์ซ้ำกับสิ่งที่เหลืออยู่ และเมื่อต้องการปัดมันออกจะมีวิธีการดั้งเดิมสำหรับการต่อท้ายNบรรทัดอินพุตเข้ากับพื้นที่รูปแบบหลังจาก\nอักขระ ewline ที่แทรก

เพื่อให้หนึ่งบรรทัดของsedควรเป็นสิ่งที่คุณต้องการ คุณเพียงแค่แทนที่matchด้วย regexp ของคุณและคุณเป็นทอง นั่นควรเป็นทางออกที่รวดเร็วเช่นกัน

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


1
7match
8
11match

เพื่อให้มันทำงานได้กับจำนวนบรรทัดโดยพลการสิ่งที่คุณต้องทำคือได้รับโอกาสในการขาย

ดังนั้น:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

... ลบ 5 บรรทัดก่อนหน้าการแข่งขันใด ๆ


1

การใช้man 1 ed:

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.