วิธีลบบรรทัดใหม่หลายรายการที่ EOF


25

ฉันมีไฟล์ที่ลงท้ายด้วยการขึ้นบรรทัดใหม่อย่างน้อยหนึ่งบรรทัดและควรลงท้ายด้วยการขึ้นบรรทัดใหม่เพียงครั้งเดียว ฉันจะทำสิ่งนั้นด้วยเครื่องมือ Bash / Unix / GNU ได้อย่างไร

ตัวอย่างไฟล์ที่ไม่ดี:

1\n
\n
2\n
\n
\n
3\n
\n
\n
\n

ตัวอย่างไฟล์ที่แก้ไข:

1\n
\n
2\n
\n
\n
3\n

กล่าวอีกนัยหนึ่ง: ควรมีการขึ้นบรรทัดใหม่หนึ่งบรรทัดระหว่าง EOF และอักขระที่ไม่ใช่บรรทัดใหม่ล่าสุดของไฟล์

การดำเนินการอ้างอิง

อ่านเนื้อหาไฟล์ตัดบรรทัดใหม่หนึ่งบรรทัดจนไม่มีบรรทัดใหม่อีกสองบรรทัดในตอนท้ายเขียนมันกลับมา:

#! /bin/python

import sys

with open(sys.argv[1]) as infile:
    lines = infile.read()

while lines.endswith("\n\n"):
    lines = lines[:-1]

with open(sys.argv[2], 'w') as outfile:
    for line in lines:
        outfile.write(line)

การชี้แจง: แน่นอนว่าการอนุญาตให้ใช้ท่อหากทำได้ดีกว่า

คำตอบ:


16
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file

2
+1: โซลูชั่นของ awk นั้นเกือบจะสวยงามและอ่านง่าย!
Olivier Dulac

@OlivierDulac แน่นอน เมื่อฉันเห็นsedข้อเสนอฉันแค่คิดว่า OMG ...
Hauke ​​Laging

1
สิ่งนี้ใช้ไม่ได้กับ OSX Mavericks โดยใช้ awk ล่าสุดที่มีจาก Homebrew awk: illegal statementมันมีข้อผิดพลาด brew install mawkและการเปลี่ยนคำสั่งให้ใช้mawkงานได้
tjmcewan

@noname ฉันไม่เข้าใจคำถาม ...
Hauke ​​Laging

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

21

จากประโยชน์สคริปต์หนึ่งบรรทัดสำหรับ sed

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file

4
ขอบคุณฉันใช้สิ่งต่อไปนี้เพื่อทำหลายไฟล์: find . -type f -name '*.js' -exec sed --in-place -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
jakub.g

@ jakub.g เข้ามาและเรียกซ้ำเป็นสิ่งที่ฉันต้องการ ขอขอบคุณ.
Buttle Butkus

เพื่อเพิ่มความคิดเห็นที่ยอดเยี่ยมจาก @ jakub.g คุณสามารถเรียกใช้คำสั่งเช่นนี้ใน OS X:find . -type f -name '*.js' -exec sed -i '' -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
davejagoda

18

เนื่องจากคุณมีคำตอบอยู่แล้วกับเครื่องมือที่เหมาะสมกว่าคือ sed และ awk; คุณสามารถใช้ประโยชน์จากความจริงที่ดึง$(< file)แถบว่างเปล่าต่อท้าย

a=$(<file); printf '%s\n' "$a" > file

การแฮ็กราคาถูกนั้นจะไม่ทำงานเพื่อลบบรรทัดว่างต่อท้ายซึ่งอาจมีช่องว่างหรืออักขระที่ไม่พิมพ์อื่น ๆ เพียงเพื่อลบบรรทัดว่างต่อท้าย มันจะไม่ทำงานหากไฟล์นั้นมีค่าเป็น null

ในเปลือกหอยอื่น ๆ นอกเหนือจากการทุบตีและ zsh ใช้แทน$(cat file)$(<file)


+1 เพื่อชี้ให้เห็นสิ่งที่ดูเหมือนข้อผิดพลาดสำหรับฉัน: $ (<file) ไม่ได้อ่านไฟล์จริงๆเหรอ? เหตุใดจึงยกเลิกการขึ้นบรรทัดใหม่ (ใช่ฉันเพิ่งทดสอบขอขอบคุณที่ชี้ให้เห็น!)
Olivier Dulac

2
@OlivierDulac $()ละเว้นการขึ้นบรรทัดใหม่ นั่นคือการตัดสินใจออกแบบ ฉันคิดว่าสิ่งนี้จะทำให้การรวมในสายอื่น ๆ ง่ายขึ้น: echo "On $(date ...) we will meet."จะชั่วร้ายกับการขึ้นบรรทัดใหม่ที่เกือบทุกคำสั่งเชลล์ส่งออกในตอนท้าย
Hauke ​​Laging

@HaukeLaging: จุดดีมันอาจเป็นต้นเหตุของพฤติกรรมนั้น
Olivier Dulac

ฉันเพิ่มเป็นกรณีพิเศษเพื่อหลีกเลี่ยงการผนวก "\ n" [[ $a == '' ]] || printf '%s\n' "$a" >"$file"เพื่อล้างไฟล์:
davidchambers

หากต้องการตัดบรรทัดใหม่หลายบรรทัดออกจากจุดเริ่มต้นของไฟล์ให้แทรก tac ลงในกระบวนการ (ฉันใช้ gnu coreutils บน Mac ดังนั้น gtac สำหรับฉัน):a=$(gtac file.txt); printf '%s\n' "$a" | gtac > file.txt
r_alex_hall


4

คำถามนี้ถูกแท็กด้วยแต่ไม่มีใครเสนอedวิธีแก้ปัญหา

นี่คือหนึ่ง:

ed -s file <<'ED_END'
a

.
?^..*?+1,.d
w
ED_END

หรือเทียบเท่า

printf '%s\n' a '' . '?^..*?+1,.d' w | ed -s file

ed จะวางคุณที่บรรทัดสุดท้ายของบัฟเฟอร์การแก้ไขโดยค่าเริ่มต้นเมื่อเริ่มต้น

คำสั่งแรก ( a) เพิ่มบรรทัดว่างที่ท้ายของบัฟเฟอร์ (บรรทัดว่างในสคริปต์การแก้ไขคือบรรทัดนี้และ dot ( .) เป็นเพียงสำหรับการกลับมาที่โหมดคำสั่ง)

คำสั่งที่สอง ( ?) ค้นหาบรรทัดก่อนหน้าที่ใกล้ที่สุดที่มีบางสิ่ง (แม้กระทั่งอักขระช่องว่างสีขาว) จากนั้นลบทุกอย่างจนสุดบัฟเฟอร์จากบรรทัดถัดไป

คำสั่งที่สาม ( w) เขียนไฟล์กลับไปที่ดิสก์

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


3

ต่อไปนี้เป็นโซลูชัน Perl ที่ไม่ต้องการการอ่านมากกว่าหนึ่งบรรทัดในหน่วยความจำในแต่ละครั้ง:

my $n = 0;
while (<>) {
    if (/./) {
        print "\n" x $n, $_;
        $n = 0;
    } else {
        $n++;
    }
}

หรือเป็นหนึ่งซับ:

perl -ne 'if (/./) { print "\n" x $n, $_; $n = 0 } else { $n++ }'

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

ในทางเทคนิคการบัฟเฟอร์แม้แต่บรรทัดเดียวในหน่วยความจำก็ไม่จำเป็น มันจะเป็นไปได้ที่จะแก้ปัญหานี้โดยใช้หน่วยความจำจำนวนคงที่โดยการอ่านไฟล์ในส่วนที่มีความยาวคงที่และประมวลผลอักขระด้วยตัวอักษรโดยใช้เครื่องสถานะ อย่างไรก็ตามฉันสงสัยว่ามันจะไม่ซับซ้อนสำหรับกรณีการใช้งานทั่วไป


1

หากไฟล์ของคุณมีขนาดเล็กพอที่จะเล็ดลอดเข้าสู่หน่วยความจำคุณสามารถใช้สิ่งนี้

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file

0

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

#!/bin/python
import sys
infile = open(sys.argv[1], 'r+')
infile.seek(-1, 2)
while infile.read(1) == '\n':
  infile.seek(-2, 1)
infile.seek(1, 1)
infile.truncate()
infile.close()

โปรดทราบว่ามันไม่สามารถใช้งานได้กับไฟล์ที่อักขระ EOL ไม่ใช่ '\ n'


0

เวอร์ชันทุบตีการนำอัลกอริทึมของไพ ธ อนมาใช้ แต่มีประสิทธิภาพน้อยลงเนื่องจากมันต้องการกระบวนการหลายอย่าง:

#!/bin/bash
n=1
while test "$(tail -n $n "$1")" == ""; do
  ((n++))
done
((n--))
truncate -s $(($(stat -c "%s" "$1") - $n)) "$1"

0

อันนี้พิมพ์ได้อย่างรวดเร็วและถ้าคุณรู้ว่า sed ง่ายต่อการจดจำ:

tac < file | sed '/[^[:blank:]]/,$!d' | tac

มันใช้สคริปต์ sed เพื่อลบบรรทัดว่างนำหน้าจากสคริปต์บรรทัดเดียวที่มีประโยชน์สำหรับ sedอ้างอิงโดย Alexey ด้านบนและ tac (reverse cat)

ในการทดสอบอย่างรวดเร็วในไฟล์ขนาด 18MB, 64,000 บรรทัดแนวทางของ Alexey นั้นเร็วขึ้น (0.036 เทียบกับ 0.046 วินาที)

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