วิธีลบบรรทัดหากมีอักขระหนึ่งครั้ง


10

ฉันต้องการลบบรรทัดออกจากไฟล์ที่มีอักขระเฉพาะเพียงครั้งเดียวหากมีมากกว่าหนึ่งครั้งหรือไม่มีอยู่ให้เก็บบรรทัดไว้ในไฟล์

ตัวอย่างเช่น:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

ที่นี่ตัวละครที่ฉันต้องการลบนั้นเป็นCเช่นนั้นคำสั่งควรลบบรรทัดFGTHDCและJUTDYCเพราะพวกเขามีCเพียงครั้งเดียว

ฉันจะทำสิ่งนี้โดยใช้อย่างใดอย่างหนึ่งsedหรือawk?

คำตอบ:


20

ในawkคุณสามารถตั้งค่าตัวคั่นฟิลด์เป็นอะไรก็ได้ หากคุณตั้งค่าให้Cแล้วคุณจะได้เป็นหลายสาขา 1 Cเป็นการเกิดขึ้นของ

ดังนั้นถ้าคุณบอกว่าawk -F'C' '{print NF}' <<< "C1C2C3"คุณได้รับ4: CCCประกอบด้วย 3 Cวินาทีและ 4 สาขา

คุณต้องการลบบรรทัดที่Cเกิดขึ้นหนึ่งครั้ง คำนึงถึงเรื่องนี้ในกรณีของคุณคุณจะต้องลบบรรทัดเหล่านั้นที่มีสองCช่อง ดังนั้นเพียงแค่ข้ามพวกเขา

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD

4
ใช้awkตัวคั่นฟิลด์อย่างชาญฉลาด!
Valentin B.

interresting เช่นในกรณีเริ่มต้น (FS = "") จะไม่สนใจช่องว่างนำหน้า ($ 1 = ไม่ใช่ช่องว่างแรกในบรรทัด) และยังทำซ้ำ (คุณสามารถมี 5 ช่องว่างเพื่อแยกฟิลด์ 1 และฟิลด์ 2) ... space อาจได้รับการดูแลเป็นพิเศษ? (เพื่อดูว่ามีใครสามารถทำได้awk 'BEGIN { print "FS={" FS"}","OFS={" OFS "}";} {printf "%d fields : ",NF; for (i=1;i<=NF;i++) {printf "{" $i "} ";}; print "" }'และให้อาหารมันบางบรรทัดบางคนมีหลาย spces และคนอื่น ๆ เริ่มต้นด้วยช่องว่าง)
Olivier Dulac

2
@OlivierDulac ใช่พื้นที่มีการจัดการเป็นพิเศษตามที่ระบุโดย POSIX
Wildcard

8

วิธีการ sed :

sed -i '/^[^C]*C[^C]*$/d' input

-i ตัวเลือกช่วยให้การปรับเปลี่ยนไฟล์ในสถานที่

/^[^C]*C[^C]*$/- จับคู่บรรทัดที่มีCเพียงครั้งเดียว

d - ลบบรรทัดที่ตรงกัน


8

ซึ่งสามารถทำได้ด้วยsed:

รหัส:

sed '/C.*C/p;/C/d' file1

ผล:

DTHGTY
HYTRHD
HTCCYD

อย่างไร?

  1. จับคู่และพิมพ์บรรทัดใด ๆ ที่มีอย่างน้อยสองสำเนาCผ่าน/C.*C/p
  2. ลบบรรทัดใด ๆ ด้วยCผ่าน/C/dซึ่งรวมถึงบรรทัดที่พิมพ์ไว้แล้วในขั้นตอนที่ 1
  3. เริ่มต้นพิมพ์ส่วนที่เหลือของบรรทัด

2
แนวทางทางเลือกที่ฉลาด ฉันชอบมัน.
สัญลักษณ์ตัวแทน

6

สิ่งนี้จะลบเส้นที่เกิดขึ้นหนึ่ง C

grep -v '^[^C]*C[^C]*$' file

นิพจน์ทั่วไป[^C]ตรงกับอักขระหนึ่งตัวที่ไม่ใช่ C (หรือขึ้นบรรทัดใหม่) และโอเปอเรเตอร์การซ้ำ (หรือที่เรียกว่า Kleene star) *ระบุการซ้ำซ้อนของการแสดงออกก่อนหน้าเป็นศูนย์หรือมากกว่า

เอาต์พุตเริ่มต้นจากgrep(และเครื่องมือเชิงข้อความอื่น ๆ ส่วนใหญ่) เป็นเอาต์พุตมาตรฐาน เปลี่ยนเส้นทางไปยังไฟล์ใหม่และอาจย้ายไปอยู่ด้านบนของไฟล์ต้นฉบับหากนั่นคือสิ่งที่คุณต้องการ regex เดียวกันสามารถใช้กับsed -iการแก้ไขในสถานที่:

sed -i '/^[^C]*C[^C]*$/d' file

(ในบางแพลตฟอร์มโดยเฉพาะอย่างยิ่ง * BSD รวมถึง macOS -iตัวเลือกต้องมีอาร์กิวเมนต์เช่น-i '')


1
sed -i '/^[^C]*C[^C]*$/d' file- ดูเหมือนว่ามันโพสต์ก่อนหน้านี้คุณคิดว่าการลอกเลียนแบบ?
RomanPerekhrest

1
แน่นอนว่ามีการทำซ้ำบางอย่าง ฉันเริ่มด้วยgrepคำตอบ แต่เห็นได้ชัดว่ามันขยายไปถึงsed -iตัวแปรได้อย่างง่ายดาย ไม่เห็นคำตอบของคุณเพราะฉันกำลังมองหาgrepคำตอบก่อนหน้า
tripleee

1
มันปลอดภัยที่จะเพียงแค่หลีกเลี่ยงชัดถ้อยชัดคำ-iมีsedและแทนที่จะเปลี่ยนเส้นทางไปยังแฟ้มใหม่และแทนที่เดิมที่มีว่าหากsedยูทิลิตี้ออกด้วยไม่มีข้อผิดพลาด
Kusalananda

2
หรือgrep -vx '[^C]*C[^C]*'
Stéphane Chazelas

@ Kusalananda แต่คุณก็อาจใช้เช่นกันgrepเพราะมันชัดเจนและมีประสิทธิภาพมากกว่า (โดยเฉพาะsedมีรหัสทางออกที่ให้ข้อมูลน้อยกว่า)
tripleee

4

เครื่องมือ POSIX สำหรับการแก้ไขสคริปต์ของไฟล์ (แทนที่จะพิมพ์เนื้อหาที่แก้ไขจะออกมาตรฐาน) exคือ

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

แน่นอนว่าคุณสามารถใช้งานได้sed -iหากเวอร์ชันของ Sed รองรับเพียงแค่ทราบว่าไม่สามารถพกพาได้หากคุณกำลังเขียนสคริปต์ที่ตั้งใจจะใช้กับระบบประเภทต่างๆ


David Foerster ถามในความคิดเห็น:

มีเหตุผลว่าทำไมคุณใช้printfและไม่ได้echoหรือสิ่งที่ต้องการex -c COMMAND?

คำตอบ: ใช่

สำหรับprintfเทียบกับechoมันเป็นคำถามของการพกพา; ดูทำไม printf ถึงดีกว่า echo printfและก็ยังง่ายต่อการขึ้นบรรทัดใหม่กระจายระหว่างคำสั่งใช้

สำหรับprintf ... | exเทียบกับex -c ...มันเป็นคำถามของการจัดการข้อผิดพลาด สำหรับคำสั่งเฉพาะนี้มันจะไม่สำคัญ แต่โดยทั่วไปแล้วมันจะ; ตัวอย่างเช่นลองวาง

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

ในสคริปต์ ตรงกันข้ามกับสิ่งต่อไปนี้:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

คนแรกจะแขวนและรอการป้อนข้อมูล; ที่สองจะออกเมื่อexคำสั่งEOF ได้รับดังนั้นสคริปต์จะดำเนินการต่อ มีวิธีแก้ไขปัญหาอื่นเช่นs///eแต่ไม่ได้ระบุโดย POSIX ฉันชอบที่จะใช้แบบพกพาซึ่งแสดงไว้ด้านบน

สำหรับgคำสั่งนั้นจะต้องมีการขึ้นบรรทัดใหม่ในตอนท้ายและฉันชอบที่printfจะใช้เพื่อตัดคำสั่งแทนการฝังบรรทัดใหม่ในเครื่องหมายคำพูดเดี่ยว


1
มีเหตุผลว่าทำไมคุณใช้printfและไม่ได้echoหรือสิ่งที่ต้องการex -c COMMAND?
David Foerster

@DavidFoerster ใช่ ฉันเริ่มที่จะตอบคุณในความคิดเห็น แต่มันใช้เวลานานดังนั้นฉันจึงเพิ่มมันเข้าไปในคำตอบ
Wildcard

ขอบคุณและ +1! ฉันรู้เกี่ยวprintfกับ vs. echo(แม้ว่าฉันมักจะชอบechoเมื่ออาร์กิวเมนต์นั้นมีการเข้ารหัสยาก) แต่ฉันยังไม่ได้ใช้exจนถึงตอนนี้
David Foerster

2

นี่คือสองตัวเลือกโดยใช้ Perl

เนื่องจากคุณกำลังจับคู่อักขระเพียงตัวเดียวคุณสามารถใช้tr/C//(แปลโดยไม่มีการแทนที่) เพื่อส่งคืนจำนวนการจับคู่ของC:

perl -lne 'print if tr/C// != 1' file

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

perl -lne 'print if (@m = /C/g) != 1' file

นี้กำหนดการแข่งขันของการแสดงออกปกติ/C/gในรายการและพิมพ์เส้นเมื่อความยาวของรายการที่ไม่ได้เป็น@m1

-iสวิทช์สามารถเพิ่มเพื่อแก้ไข "ในสถานที่"


2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'

โปรดทราบว่าจะอนุมานว่า GNU sed, t #...มักจะต้องมีสาขาให้กับต้นสังกัดเรียกว่า#...ในอื่น ๆ ส่วนใหญ่sedการใช้งาน
Stéphane Chazelas

แม้แต่!bGNU ที่ถูกต้องเนื่องจากสาขาไม่ชอบอะไรเลยยกเว้นป้ายกำกับหรือการขึ้นบรรทัดใหม่หลังจากนั้น

ใช่b, t, :, }(และr file, w file... ) ไม่สามารถมีคำสั่งหลังจากที่พวกเขาในบรรทัดเดียวกัน นอกจากนี้คุณยังสามารถใช้-eตัวเลือกแยก
Stéphane Chazelas

ตัวเลือกของคุณไม่ได้ให้ผลลัพธ์ที่ถูกต้อง ฉันเดาว่าคุณลืมเพิ่มgตัวแก้ไข
Tom Fenech

@ TomFenech คุณถูกต้อง ฉันกำลังแก้ไขที่ ขอบคุณ

1

สำหรับทุกคนที่ต้องการawkโดยเฉพาะฉันจะให้

awk '/C[^C]*C/{next}//{print}'

ข้ามบรรทัดหากตรงกับรูปแบบให้พิมพ์เป็นอย่างอื่น คุณไม่ต้องการจริงๆ{print}คุณสามารถใช้//และพิมพ์ค่าเริ่มต้นได้ แต่ฉันคิดว่ามันชัดเจนกว่านี้

ความคิดแรกของฉันคือใช้egrep -vกับรูปแบบเดียวกัน แต่นั่นไม่ได้ตอบคำถามที่ถูกวาง


1
สิ่งที่จุดของการจับคู่อะไรหลังจากที่{next}? เพียงพูดawk '/pattern/ {next} 1'และทุกบรรทัดที่ไม่ตรงกับรูปแบบจะถูกพิมพ์ หรือดีกว่าawk '!/pattern/'เพื่อพิมพ์โดยตรง
fedorqui

@fedorqui จุดที่ดีเกี่ยวกับ!/pattern/(ซึ่งอย่างใดเล็ดรอดใจของฉัน) แต่ฉันค่อนข้างไกลเห็นอธิบายตนเองกว่าคลุมเครือ//{print} 1สมมติว่าความสามารถและความคล่องแคล่วน้อยที่สุดจากบุคคลถัดไปเพื่อรักษารหัสของคุณให้สอดคล้องกับการไม่ทำให้มีประสิทธิภาพหรือประสิทธิผลน้อยลง
nigel222
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.