Bagaimana cara menghapus beberapa baris baru di EOF?


25

Saya memiliki file yang diakhiri dengan satu atau lebih baris baru dan harus berakhir hanya dalam satu baris baru. Bagaimana saya bisa melakukannya dengan alat Bash / Unix / GNU?

Contoh file buruk:

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

Contoh file yang diperbaiki:

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

Dengan kata lain: Harus ada tepat satu baris baru antara EOF dan karakter non-baris terakhir file.

Implementasi Referensi

Baca konten file, potong satu baris baru sampai tidak ada dua baris lagi di akhir, tulis kembali:

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

Klarifikasi: Tentu saja, perpipaan diperbolehkan, jika itu lebih elegan.

Jawaban:


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

2
+1: solusi awk (hampir) selalu elegan dan mudah dibaca!
Olivier Dulac

@OlivierDulac Memang. Ketika saya melihat sedproposal saya hanya berpikir OMG ...
Hauke ​​Laging

1
ini tidak berfungsi pada OSX Mavericks menggunakan awk yang tersedia terbaru dari Homebrew. Kesalahan dengan awk: illegal statement. brew install mawkdan mengubah perintah untuk mawkbekerja.
tjmcewan

@ Noname Saya bahkan tidak mengerti pertanyaannya ...
Hauke ​​Laging

Awk yang skripnya tidak berfungsi adalah awk yang rusak parah - hentikan penggunaannya dan dapatkan awk baru karena jika tidak bisa melakukan ini maka siapa yang tahu kerusakan apa yang dimilikinya.
Ed Morton

21

Dari skrip satu-baris yang berguna untuk sed .

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

4
Terima kasih, saya menggunakan yang berikut ini untuk melakukannya di beberapa file: find . -type f -name '*.js' -exec sed --in-place -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
jakub.g

@ jakub.g di tempat dan rekursif adalah persis apa yang saya butuhkan. Terima kasih.
Buttle Butkus

Untuk menambah komentar luar biasa dari @ jakub.g Anda dapat menjalankan perintah seperti ini di OS X:find . -type f -name '*.js' -exec sed -i '' -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
davejagoda

18

Karena Anda sudah memiliki jawaban dengan alat yang lebih cocok, sed and awk; Anda bisa mengambil keuntungan dari kenyataan bahwa $(< file)strip off trailing blank.

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

Peretasan murah itu tidak akan berfungsi untuk menghapus jejak kosong yang mungkin mengandung spasi atau karakter non-cetak lainnya, hanya untuk menghapus jejak kosong. Ini juga tidak akan berfungsi jika file berisi null byte.

Dalam shell selain bash dan zsh, gunakan $(cat file)sebagai ganti $(<file).


+1 untuk menunjukkan apa yang tampak seperti bug bagi saya: $ (<file) tidak benar-benar membaca file? mengapa itu membuang baris baru? (Ya, saya baru saja menguji, terima kasih telah menunjukkannya!)
Olivier Dulac

2
@OlivierDulac $()membuang baris baru. Itu keputusan desain. Saya berasumsi bahwa ini akan membuat integrasi dalam string lain lebih mudah: echo "On $(date ...) we will meet."akan menjadi jahat dengan baris baru yang hampir setiap perintah shell menghasilkan di akhir.
Hauke ​​Laging

@ HaukeLaging: Poin bagus, mungkin itu sumber perilaku itu
Olivier Dulac

Saya menambahkan kasus khusus untuk menghindari menambahkan "\ n" untuk mengosongkan file: [[ $a == '' ]] || printf '%s\n' "$a" >"$file".
davidchambers

Untuk menghapus beberapa baris baru dari awal file, masukkan tac ke dalam proses (saya menggunakan gnu coreutils pada Mac, jadi gtac untuk saya):a=$(gtac file.txt); printf '%s\n' "$a" | gtac > file.txt
r_alex_hall


4

Pertanyaan ini ditandai dengan , tetapi tidak ada yang mengusulkan edsolusi.

Ini dia:

ed -s file <<'ED_END'
a

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

atau, yang setara,

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

ed akan menempatkan Anda pada baris terakhir dari buffer pengeditan secara default saat startup.

Perintah pertama ( a) menambahkan baris kosong ke ujung buffer (baris kosong dalam skrip pengeditan adalah baris ini, dan titik (. ) hanya untuk kembali ke mode perintah).

Perintah kedua (? ) mencari baris terdekat sebelumnya yang berisi sesuatu (bahkan karakter spasi putih), dan kemudian menghapus semuanya sampai akhir buffer dari baris berikutnya.

Perintah ketiga (w ) menulis file kembali ke disk.

Baris kosong yang ditambahkan melindungi sisa file agar tidak terhapus jika tidak ada baris kosong di akhir file asli.


3

Berikut adalah solusi Perl yang tidak memerlukan membaca lebih dari satu baris ke memori sekaligus:

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

atau, sebagai one-liner:

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

Ini membaca file satu baris pada satu waktu dan memeriksa setiap baris untuk melihat apakah mengandung karakter non-baris baru. Jika tidak, itu menambah penghitung; jika ya, ia akan mencetak jumlah baris baru yang ditunjukkan oleh penghitung, diikuti oleh baris itu sendiri, dan kemudian mengatur ulang penghitung.

Secara teknis, bahkan buffering satu baris dalam memori tidak perlu; akan mungkin untuk memecahkan masalah ini menggunakan jumlah memori yang konstan dengan membaca file dalam potongan-potongan tetap dan memprosesnya karakter dengan karakter menggunakan mesin negara. Namun, saya menduga itu tidak perlu rumit untuk kasus penggunaan biasa.


1

Jika file Anda cukup kecil untuk menghirup memori, Anda dapat menggunakan ini

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

0

Dalam python (saya tahu itu bukan apa yang Anda inginkan, tetapi jauh lebih baik karena dioptimalkan, dan pendahuluan ke versi bash) tanpa menulis ulang file dan tanpa membaca semua file (yang merupakan hal yang baik jika file tersebut adalah sangat besar):

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

Perhatikan bahwa ini tidak berfungsi pada file di mana karakter EOL bukan '\ n'.


0

Versi bash, mengimplementasikan algoritma python, tetapi kurang efisien karena membutuhkan banyak proses:

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

0

Ini cepat untuk mengetik, dan, jika Anda tahu sed, mudah diingat:

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

Ia menggunakan skrip sed untuk menghapus baris kosong terkemuka dari skrip satu baris berguna untuk sed , dirujuk oleh Alexey, di atas, dan tac (reverse cat).

Dalam tes cepat, pada 18MB, 64.000 file baris, pendekatan Alexey lebih cepat, (0,036 vs 0,046 detik).

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.