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.

Bengt
sumber

Jawaban:

16
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file
Hauke ​​Laging
sumber
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
Alexey Shmalko
sumber
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).

llua
sumber
+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
5

Anda dapat menggunakan trik ini dengan cat& printf:

$ printf '%s\n' "`cat file`"

Sebagai contoh

$ printf '%s\n' "`cat ifile`" > ofile
$ cat -e ofile
1$
$
2$
$
$
3$

Yang $menunjukkan akhir garis.

Referensi

slm
sumber
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.

Kusalananda
sumber
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.

Ilmari Karonen
sumber
1

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

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file
terdon
sumber
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'.

jfg956
sumber
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"
jfg956
sumber
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).

gratisB
sumber