Bagaimana saya bisa menghapus baris baru jika itu adalah karakter terakhir dalam file?


162

Saya memiliki beberapa file yang ingin saya hapus baris baru terakhir jika itu adalah karakter terakhir dalam file. od -cmemperlihatkan kepada saya bahwa perintah yang saya jalankan tidak menulis file dengan baris baru yang tertinggal:

0013600   n   t  >  \n

Saya sudah mencoba beberapa trik dengan sed tetapi yang terbaik yang bisa saya pikirkan adalah tidak melakukan trik:

sed -e '$s/\(.*\)\n$/\1/' abc

Ada ide bagaimana melakukan ini?


4
baris baru hanya satu karakter untuk baris baru unix. Baris baru DOS adalah dua karakter. Tentu saja, literal "\ n" adalah dua karakter. Apa yang sebenarnya Anda cari?
Dijeda sampai pemberitahuan lebih lanjut.

3
Meskipun representasi mungkin \n, di linux adalah satu karakter
pavium

10
Bisakah Anda menguraikan mengapa Anda ingin melakukan ini? File teks seharusnya diakhiri dengan end-of-line, kecuali jika semuanya kosong. Sepertinya aneh bagi saya bahwa Anda ingin memiliki file terpotong?
Thomas Padron-McCarthy

Alasan biasa untuk melakukan sesuatu seperti ini adalah untuk menghapus koma tertinggal dari baris terakhir file CSV. Sed bekerja dengan baik, tetapi baris baru harus diperlakukan berbeda.
pavium

9
@ ThomasPadron-McCarthy "Dalam komputasi, untuk setiap alasan yang baik ada untuk melakukan sesuatu, ada alasan bagus untuk tidak melakukannya dan sebaliknya visa." -Yesus - "Anda tidak harus melakukan itu" adalah jawaban yang mengerikan tidak peduli pertanyaannya. Format yang benar adalah: [bagaimana melakukannya] tetapi [mengapa itu mungkin ide yang buruk]. #sacrilege
Cory Mawhorter

Jawaban:


223
perl -pe 'chomp if eof' filename >filename2

atau, untuk mengedit file di tempat:

perl -pi -e 'chomp if eof' filename

[Catatan Editor: -pi -epada awalnya -pie, tetapi, sebagaimana dicatat oleh beberapa komentator dan dijelaskan oleh @hvd, yang terakhir tidak berfungsi.]

Ini digambarkan sebagai 'penghujatan perl' di situs web awk yang saya lihat.

Tapi, dalam ujian, itu berhasil.


11
Anda dapat membuatnya lebih aman dengan menggunakan chomp. Dan itu mengalahkan menghirup file.
Sinan Ünür

6
Meskipun penistaan, itu bekerja dengan sangat baik. perl -i -pe 'chomp if eof' nama file. Terima kasih.
Todd Partridge 'Gen2ly'

13
Hal lucu tentang penistaan ​​dan bidat adalah biasanya dibenci karena itu benar. :)
Eter

8
Koreksi kecil: Anda dapat menggunakan perl -pi -e 'chomp if eof' filename, untuk mengedit file di tempat alih-alih membuat file sementara
Romuald Brunet

7
perl -pie 'chomp if eof' filename-> Tidak dapat membuka skrip perl "chomp if eof": Tidak ada file atau direktori; perl -pi -e 'chomp if eof' filename-> works
aditsu berhenti karena SE adalah 1 JAHAT

56

Anda dapat memanfaatkan fakta bahwa pergantian perintah shell menghapus karakter baris baru :

Bentuk sederhana yang bekerja di bash, ksh, zsh:

printf %s "$(< in.txt)" > out.txt

Alternatif portable (POSIX-compliant) (sedikit kurang efisien):

printf %s "$(cat in.txt)" > out.txt

catatan:

  • Jika in.txtujung dengan beberapa karakter baris baru, substitusi perintah menghapus semua dari mereka - terima kasih, @Sparhawk. (Itu tidak menghapus karakter spasi selain dari mengikuti baris baru.)
  • Karena pendekatan ini membaca seluruh file input ke dalam memori , maka hanya disarankan untuk file yang lebih kecil.
  • printf %smemastikan bahwa tidak ada baris baru yang ditambahkan ke output (itu adalah alternatif yang sesuai dengan POSIX untuk yang tidak standar echo -n; lihat http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html dan https: //unix.stackexchange. com / a / 65819 )

Sebuah panduan untuk jawaban yang lain :

  • Jika Perl tersedia, cari jawaban yang diterima - sederhana dan hemat memori (tidak membaca seluruh file input sekaligus).

  • Jika tidak, pertimbangkan ghostdog74 ini AWK jawaban - itu jelas, tetapi juga hemat memori ; a lebih mudah dibaca setara (POSIX-compliant) adalah:

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • Pencetakan tertunda oleh satu baris sehingga baris terakhir dapat ditangani di ENDblok, di mana ia dicetak tanpa \njejak karena pengaturan pemisah catatan keluaran ( OFS) ke string kosong.
  • Jika Anda menginginkan solusi verbose, tetapi cepat dan tangguh yang benar - benar diedit di tempat (sebagai lawan membuat temp. File yang kemudian menggantikan yang asli), pertimbangkan skrip Perl jrockway .


3
NB jika ada beberapa baris baru di akhir file, perintah ini akan menghapus semuanya.
Sparhawk

47

Anda dapat melakukan ini dengan headdari GNU coreutils, ia mendukung argumen yang relatif terhadap akhir file. Jadi untuk meninggalkan penggunaan byte terakhir:

head -c -1

Untuk menguji baris akhir yang berakhir, Anda dapat menggunakan taildan wc. Contoh berikut menyimpan hasil ke file sementara dan selanjutnya menimpa yang asli:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
  head -c -1 file > file.tmp
  mv file.tmp file
fi

Anda juga dapat menggunakan spongedari mulai moreutilsmengedit "di tempat":

[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

Anda juga dapat membuat fungsi yang dapat digunakan kembali secara umum dengan memasukkan ini di .bashrcfile Anda :

# Example:  remove-last-newline < multiline.txt
function remove-last-newline(){
    local file=$(mktemp)
    cat > $file
    if [[ $(tail -c1 $file | wc -l) == 1 ]]; then
        head -c -1 $file > $file.tmp
        mv $file.tmp $file
    fi
    cat $file
}

Memperbarui

Seperti dicatat oleh KarlWilbur dalam komentar dan digunakan dalam jawaban Sorentar , truncate --size=-1dapat menggantikan head -c-1dan mendukung pengeditan di tempat.


3
Solusi terbaik sejauh ini. Menggunakan alat standar yang benar-benar dimiliki oleh setiap distribusi Linux, dan ringkas dan jelas, tanpa sihir sed atau perl.
Dakkaron

2
Solusi bagus Satu perubahan adalah saya pikir saya akan menggunakan truncate --size=-1daripada head -c -1hanya mengubah ukuran file input daripada membaca di file input, menuliskannya ke file lain, lalu mengganti yang asli dengan file output.
Karl Wilbur

1
Catatan yang head -c -1akan menghapus karakter terakhir terlepas dari apakah itu baris baru atau tidak, itu sebabnya Anda harus memeriksa apakah karakter terakhir adalah baris baru sebelum Anda menghapusnya.
wisbucky

Sayangnya tidak berfungsi di Mac. Saya menduga itu tidak bekerja pada varian BSD.
Edward Falk

16
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

Edit 2:

Berikut ini adalah awkversi (dikoreksi) yang tidak mengumpulkan array yang berpotensi besar:

awk '{if (line) line print; line = $ 0} END {printf $ 0} 'abc


Cara orisinal yang bagus untuk memikirkannya. Terima kasih Dennis.
Todd Partridge 'Gen2ly'

Anda benar. Saya tunduk pada awkversi Anda . Dibutuhkan dua offset (dan tes yang berbeda) dan saya hanya menggunakan satu. Namun, Anda bisa menggunakan printfbukan ORS.
Dijeda sampai pemberitahuan lebih lanjut.

Anda dapat membuat output menjadi pipa dengan proses substitusi:head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ...
BCoates

2
Menggunakan -c bukannya -n untuk kepala dan ekor harus lebih cepat.
rudimeier

1
Bagi saya, head -n -1 abc menghapus baris aktual file yang terakhir, meninggalkan baris tambahan; head -c -1 abc tampaknya bekerja lebih baik
ChrisV

10

melongo

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file

Masih terlihat seperti banyak karakter bagi saya ... mempelajarinya dengan lambat :). Apakah pekerjaan itu baik. Terima kasih ghostdog.
Todd Partridge 'Gen2ly'

1
awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' fileini seharusnya lebih mudah dibaca.
Yevhen Pavliuk

Bagaimana: awk 'NR>1 {print p} {p=$0} END {printf $0}' file.
Isaac

@sorontar Argumen pertama printfadalah argumen format . Jadi jika file input memiliki sesuatu yang dapat diartikan sebagai penentu format seperti %d, Anda akan mendapatkan kesalahan. Perbaikan akan mengubahnya keprintf "%s" $0
Robin A. Meade

9

Metode yang sangat sederhana untuk file single-line, membutuhkan gema GNU dari coreutils:

/bin/echo -n $(cat $file)

Ini adalah cara yang layak jika tidak terlalu mahal (berulang).

Ini memiliki masalah saat \nhadir. Saat dikonversi ke baris baru.
Chris Stryczynski

Tampaknya juga berfungsi untuk file multi-line yang $(...)dikutip
Thor

pasti perlu mengutip bahwa ... /bin/echo -n "$(cat infile)" Juga, saya tidak yakin apa maks len echoatau shell akan melintasi os / versi shell / distro (saya hanya googling ini & itu adalah lubang kelinci), jadi saya tidak yakin seberapa portabel (atau performer) itu sebenarnya untuk apa pun selain file kecil - tetapi untuk file kecil, hebat.
michael

8

Jika Anda ingin melakukannya dengan benar, Anda perlu sesuatu seperti ini:

use autodie qw(open sysseek sysread truncate);

my $file = shift;
open my $fh, '+>>', $file;
my $pos = tell $fh;
sysseek $fh, $pos - 1, 0;
sysread $fh, my $buf, 1 or die 'No data to read?';

if($buf eq "\n"){
    truncate $fh, $pos - 1;
}

Kami membuka file untuk membaca dan menambahkan; membuka untuk menambahkan berarti kita sudah seekmengedit sampai akhir file. Kami kemudian mendapatkan posisi numerik dari akhir file tell. Kami menggunakan angka itu untuk mencari kembali satu karakter, dan kemudian kami membaca satu karakter itu. Jika itu adalah baris baru, kami memotong file ke karakter sebelum baris baru itu, jika tidak, kami tidak melakukan apa-apa.

Ini berjalan dalam waktu dan ruang konstan untuk input apa pun, dan juga tidak memerlukan ruang disk lagi.


2
tetapi yang memiliki kelemahan tidak reseting kepemilikan / hak akses untuk file ... err, menunggu ...
ysth

1
Verbose, tetapi keduanya cepat dan tangguh - tampaknya menjadi satu-satunya jawaban pengeditan file di tempat yang benar di sini (dan karena mungkin tidak jelas bagi semua orang: ini adalah skrip Perl ).
mklement0

6

Ini adalah solusi Python yang bagus dan rapi. Saya tidak berusaha untuk singkat di sini.

Ini memodifikasi file di tempat, daripada membuat salinan file dan menghapus baris baru dari baris terakhir salinan. Jika file berukuran besar, ini akan jauh lebih cepat daripada solusi Perl yang dipilih sebagai jawaban terbaik.

Ini memotong file dengan dua byte jika dua byte terakhir adalah CR / LF, atau satu byte jika byte terakhir adalah LF. Itu tidak berusaha untuk memodifikasi file jika byte terakhir tidak (CR) LF. Ini menangani kesalahan. Diuji dalam Python 2.6.

Masukkan ini dalam file yang disebut "striplast" dan chmod +x striplast.

#!/usr/bin/python

# strip newline from last line of a file


import sys

def trunc(filename, new_len):
    try:
        # open with mode "append" so we have permission to modify
        # cannot open with mode "write" because that clobbers the file!
        f = open(filename, "ab")
        f.truncate(new_len)
        f.close()
    except IOError:
        print "cannot write to file:", filename
        sys.exit(2)

# get input argument
if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = "--help"  # wrong number of arguments so print help

if filename == "--help" or filename == "-h" or filename == "/?":
    print "Usage: %s <filename>" % sys.argv[0]
    print "Strips a newline off the last line of a file."
    sys.exit(1)


try:
    # must have mode "b" (binary) to allow f.seek() with negative offset
    f = open(filename, "rb")
except IOError:
    print "file does not exist:", filename
    sys.exit(2)


SEEK_EOF = 2
f.seek(-2, SEEK_EOF)  # seek to two bytes before end of file

end_pos = f.tell()

line = f.read()
f.close()

if line.endswith("\r\n"):
    trunc(filename, end_pos)
elif line.endswith("\n"):
    trunc(filename, end_pos + 1)

PS Dalam semangat "Perl golf", inilah solusi Python terpendek saya. Ini menghirup seluruh file dari input standar ke dalam memori, menghapus semua baris baru pada akhirnya, dan menulis hasilnya ke output standar. Tidak sesingkat Perl; Anda tidak bisa mengalahkan Perl untuk hal-hal kecil yang rumit dan cepat seperti ini.

Hapus "\ n" dari panggilan ke .rstrip() dan itu akan menghapus semua ruang putih dari akhir file, termasuk beberapa baris kosong.

Masukkan ini ke dalam "slurp_and_chomp.py" dan kemudian jalankan python slurp_and_chomp.py < inputfile > outputfile.

import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))

os.path.isfile () akan memberi tahu Anda tentang keberadaan file. Menggunakan try / kecuali mungkin menangkap banyak kesalahan yang berbeda :)
Denis Barmenkov

5

Solusi cepat menggunakan utilitas gnu truncate:

[ -z $(tail -c1 file) ] && truncate -s-1 file

Tes akan benar jika file tersebut memang memiliki garis baru.

Penghapusannya sangat cepat, benar-benar di tempat, tidak ada file baru yang diperlukan dan pencarian juga membaca dari ujung hanya satu byte ( tail -c1).


1
truncate: operan file yang hilang
Brian Hannay

2
itu hanya kehilangan nama file yang tertinggal dalam contoh, yaitu, [ -z $(tail -c1 filename) ] && truncate -s -1 filename(juga, dalam membalas komentar lain, truncateperintah tidak bekerja dengan stdin, nama file diperlukan)
michael

4

Satu lagi perl WTDI:

perl -i -p0777we's/\n\z//' filename

3
$ perl -e 'local $ /; $ _ = <>; s / \ n $ //; cetak 'a-text-file.txt

Lihat juga Cocokkan karakter apa saja (termasuk baris baru) di sed .


1
Itu menghilangkan semua baris baru. Setara dengantr -d '\n'
Dijeda hingga pemberitahuan lebih lanjut.

Ini juga bekerja dengan baik, mungkin kurang menghujat dari pavium.
Todd Partridge 'Gen2ly'

Sinan, meskipun Linux dan Unix mungkin mendefinisikan file teks untuk diakhiri dengan baris baru, Windows tidak memiliki persyaratan seperti itu. Notepad, misalnya, hanya akan menulis karakter yang Anda ketikkan tanpa menambahkan tambahan di bagian akhir. Kompiler C mungkin memerlukan file sumber untuk diakhiri dengan jeda baris, tetapi file sumber C bukan "hanya" file teks, sehingga mereka dapat memiliki persyaratan tambahan.
Rob Kennedy

dalam nada itu, kebanyakan minifiers javascript / css akan menghapus baris baru, dan menghasilkan file teks.
ysth

@Rob Kennedy dan @ysth: Ada argumen menarik di sana mengapa file tersebut sebenarnya bukan file teks dan semacamnya.
Sinan Ünür

2

Menggunakan dd:

file='/path/to/file'
[[ "$(tail -c 1 "${file}" | tr -dc '\n' | wc -c)" -eq 1 ]] && \
    printf "" | dd  of="${file}" seek=$(($(stat -f "%z" "${file}") - 1)) bs=1 count=1
    #printf "" | dd  of="${file}" seek=$(($(wc -c < "${file}") - 1)) bs=1 count=1

2
perl -pi -e 's/\n$// if(eof)' your_file

Secara efektif sama dengan jawaban yang diterima, tetapi bisa dibilang lebih jelas dalam konsep untuk pengguna non-Perl. Perhatikan bahwa tidak ada kebutuhan untuk gatau tanda kurung di sekitar eof: perl -pi -e 's/\n$// if eof' your_file.
mklement0

2

Dengan asumsi jenis file Unix dan Anda hanya ingin baris baru terakhir ini berfungsi.

sed -e '${/^$/d}'

Itu tidak akan berfungsi pada banyak baris baru ...

* Hanya berfungsi jika baris terakhir adalah baris kosong.


Inilah sedsolusi yang berfungsi bahkan untuk baris terakhir yang tidak kosong: stackoverflow.com/a/52047796
wisbucky

1

Namun jawaban lain FTR (dan favorit saya!): Gema / hal yang Anda ingin strip dan menangkap output melalui backticks. Baris baru terakhir akan dilucuti. Sebagai contoh:

# Sadly, outputs newline, and we have to feed the newline to sed to be portable
echo thingy | sed -e 's/thing/sill/'

# No newline! Happy.
out=`echo thingy | sed -e 's/thing/sill/'`
printf %s "$out"

# Similarly for files:
file=`cat file_ending_in_newline`
printf %s "$file" > file_no_newline

1
Saya menemukan combo kucing-printf keluar secara tidak sengaja (sedang mencoba untuk mendapatkan perilaku yang berlawanan). Perhatikan bahwa ini akan menghapus SEMUA baris baru, bukan hanya yang terakhir.
technosaurus

1

POSIX SED:

'$ {/ ^ $ / d}'

$ - match last line


{ COMMANDS } - A group of commands may be enclosed between { and } characters. This is particularly useful when you want a group of commands to be triggered by a single address (or address-range) match.

Saya pikir ini hanya akan menghapusnya jika baris terakhir kosong. Itu tidak akan menghapus baris tambahan jika baris terakhir tidak kosong. Misalnya, echo -en 'a\nb\n' | sed '${/^$/d}'tidak akan menghapus apa pun. echo -en 'a\nb\n\n' | sed '${/^$/d}'akan dihapus karena seluruh baris terakhir kosong.
wisbucky

1

Ini adalah solusi yang baik jika Anda membutuhkannya untuk bekerja dengan pipa / redirection alih-alih membaca / output dari atau ke file. Ini bekerja dengan satu atau beberapa baris. Ini berfungsi apakah ada baris tambahan atau tidak.

# with trailing newline
echo -en 'foo\nbar\n' | sed '$s/$//' | head -c -1

# still works without trailing newline
echo -en 'foo\nbar' | sed '$s/$//' | head -c -1

# read from a file
sed '$s/$//' myfile.txt | head -c -1

Detail:

  • head -c -1memotong karakter terakhir dari string, terlepas dari apa karakternya. Jadi jika string tidak diakhiri dengan baris baru, maka Anda akan kehilangan karakter.
  • Jadi untuk alamat bahwa masalah, kita tambahkan perintah lain yang akan menambahkan baris baru Trailing jika tidak ada satu: sed '$s/$//'. Yang pertama $berarti hanya menerapkan perintah ke baris terakhir. s/$//berarti mengganti "ujung garis" dengan "tidak ada", yang pada dasarnya tidak melakukan apa-apa. Tetapi ini memiliki efek samping menambahkan baris baru setelah tidak ada.

Catatan: Default Mac headtidak mendukung -copsi. Anda bisa melakukannya brew install coreutilsdan menggunakannya ghead.


0

Satu-satunya waktu saya ingin melakukan ini adalah untuk kode golf, dan kemudian saya baru saja menyalin kode saya keluar dari file dan menempelkannya ke dalam echo -n 'content'>filepernyataan.


Di tengah jalan; pendekatan lengkap di sini .
mklement0


0

Saya memiliki masalah yang sama, tetapi bekerja dengan file windows dan perlu menjaga CRLF - solusi saya di linux:

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked

0
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

Seharusnya menghapus kejadian terakhir dari \ n dalam file. Tidak berfungsi pada file besar (karena keterbatasan buffer sed)


0

rubi:

ruby -ne 'print $stdin.eof ? $_.strip : $_'

atau:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
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.