Bagaimana cara saya menghapus baris tambahan di bash?


10

Saya mencari sesuatu yang berperilaku seperti Perl chomp. Saya mencari perintah yang hanya mencetak inputnya, minus karakter terakhir jika itu baris baru:

$ printf "one\ntwo\n" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done

(Substitusi perintah di Bash dan Zsh menghapus semua trailing baris baru, tapi saya sedang mencari sesuatu yang paling banyak menghapus satu trailing baris baru.)

Jawaban:


9

Ini seharusnya bekerja:

printf "one\ntwo\n" | awk 'NR>1{print PREV} {PREV=$0} END{printf("%s",$0)}' ; echo " done"

Script selalu mencetak baris sebelumnya, bukan saat ini, dan baris terakhir diperlakukan berbeda.

Apa yang dilakukannya lebih detail:

  1. NR>1{print PREV} Cetak baris sebelumnya (kecuali yang pertama kali).
  2. {PREV=$0}Menyimpan baris saat ini dalam PREVvariabel.
  3. END{printf("%s",$0)} Akhirnya, cetak baris terakhir tanpa putus baris.

Juga perhatikan ini akan menghapus paling banyak satu baris kosong di akhir (tidak ada dukungan untuk menghapus "one\ntwo\n\n\n").


15

Anda dapat menggunakan perltanpa chomp:

$ printf "one\ntwo\n" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

$ printf "one\ntwo" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

Tapi mengapa tidak menggunakannya chompsendiri:

$ printf "one\ntwo\n" | perl -pe 'chomp if eof'; echo " done"

4

Jika Anda ingin yang setara persis chomp, metode pertama yang muncul di benak saya adalah solusi awk yang sudah diposting LatinSuD . Saya akan menambahkan beberapa metode lain yang tidak menerapkan chomptetapi mengimplementasikan beberapa tugas umum yang chompsering digunakan.

Saat Anda memasukkan beberapa teks ke dalam variabel, semua baris baru di bagian akhir akan dihapus. Jadi semua perintah ini menghasilkan output single-line yang sama:

echo "$(printf 'one\ntwo') done"
echo "$(printf 'one\ntwo\n') done"
echo "$(printf 'one\ntwo\n\n') done"
echo "$(printf 'one\ntwo\n\n\n\n\n\n\n\n\n\n') done"

Jika Anda ingin menambahkan beberapa teks ke baris terakhir file atau output perintah, sedbisa nyaman. Dengan sed GNU dan sebagian besar implementasi modern lainnya, ini berfungsi bahkan jika input tidak berakhir pada baris baru¹; namun, ini tidak akan menambah baris baru jika belum ada.

sed '$ s/$/ done/'

¹ Namun ini tidak bekerja dengan semua implementasi sed: sed adalah alat pemrosesan teks, dan file yang tidak kosong dan tidak diakhiri dengan karakter baris baru bukan file teks.


Ini tidak persis sama dengan chomp, karena chomphanya menghapus paling banyak satu trailing newline.
Flimm

@ Flimm Ya, padanan pasti yang paling jelas chompadalah solusi awk yang sudah diposting LatinSuD. Tetapi dalam banyak kasus chomphanyalah alat untuk melakukan pekerjaan, dan saya menyediakan cara untuk melakukan beberapa tugas umum. Biarkan saya memperbarui jawaban saya untuk memperjelas ini.
Gilles 'SO- stop being evil'

1

perlPendekatan lain . Yang ini membaca seluruh input ke dalam memori sehingga mungkin bukan ide yang baik untuk sejumlah besar data (gunakan cuonglm atau awkpendekatan untuk itu):

$ printf "one\ntwo\n" | perl -0777pe 's/\n$//'; echo " done"
one
two done

Terima kasih, @ StéphaneChazelas, sudah diperbaiki. Untuk beberapa alasan, saklar ini selalu membingungkan saya !
terdon

0

Saya mengambil ini dari repo github di suatu tempat, tetapi tidak dapat menemukan di mana

delete-trailing-blank-lines-sed

#!/bin/bash
#
# Delete all trailing blank lines.
# From http://sed.sourceforge.net/sed1line.txt
#
# Version: 1.3.0
# Created: 2011-01-02
# Updated: 2015-01-25
# Contact: Joel Parker Henderson (joel@joelparkerhenderson.com)
# License: GPL
##
set -euf
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'

0

abstrak

Cetak baris tanpa baris baru, tambahkan baris baru hanya jika ada baris lain untuk dicetak.

$ printf 'one\ntwo\n' | 

     awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }';   echo " done"

one
two done

Solusi lain

Jika kami bekerja dengan file, kami dapat memotong satu karakter saja dari file tersebut (jika berakhir pada baris baru):

removeTrailNewline () {[[$ (tail -c 1 "$ 1")]] || truncate -s-1 "$ 1"; }

Itu adalah solusi cepat karena hanya perlu membaca satu karakter dari file dan kemudian menghapusnya langsung ( truncate) tanpa membaca seluruh file.

Namun, saat bekerja dengan data dari stdin (aliran) data harus dibaca, semuanya. Dan, "dikonsumsi" segera setelah dibaca. Tidak ada mundur (seperti dengan terpotong). Untuk menemukan akhir suatu aliran, kita perlu membaca sampai ke ujung aliran. Pada saat itu, tidak ada cara untuk kembali pada input stream, data telah "dikonsumsi". Ini berarti bahwa data harus disimpan dalam beberapa bentuk buffer sampai kami mencocokkan akhir aliran dan kemudian melakukan sesuatu dengan data dalam buffer.

Solusi yang paling jelas adalah mengubah aliran menjadi file dan memproses file itu. Tetapi pertanyaannya meminta semacam filter aliran. Bukan tentang penggunaan file tambahan.

variabel

Solusi naif adalah dengan menangkap seluruh input ke dalam variabel:

FilterOne(){ filecontents=$(cat; echo "x");        # capture the whole input
             filecontents=${filecontents%x};       # Remove the "x" added above.
             nl=$'\n';                             # use a variable for newline.
             printf '%s' "${filecontents%"$nl"}";  # Remove newline (if it exists).
       }

printf 'one\ntwo'     | FilterOne ; echo 1done
printf 'one\ntwo\n'   | FilterOne ; echo 2done
printf 'one\ntwo\n\n' | FilterOne ; echo 3done

Penyimpanan

Dimungkinkan untuk memuat seluruh file dalam memori dengan sed. Selain itu, tidak mungkin untuk menghindari baris baru yang tertinggal di baris terakhir. GNU sed mungkin menghindari pencetakan baris tambahan, tetapi hanya jika file sumber sudah hilang. Jadi, tidak, sed sederhana tidak bisa membantu.

Kecuali pada GNU awk dengan -zopsi:

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

Dengan awk (sembarang awk), hirup seluruh aliran, dan printftanpa baris baru.

awk '    { content = content $0 RS } 
     END { gsub( "\n$", "", content ); printf( "%s", content ) }
    '

Memuat seluruh file ke dalam memori mungkin bukan ide yang baik, mungkin menghabiskan banyak memori.

Dua baris dalam memori

Dalam awk, kita dapat memproses dua baris per loop dengan menyimpan baris sebelumnya dalam sebuah variabel dan mencetak yang sekarang:

awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'

Pemrosesan langsung

Tapi kita bisa melakukan yang lebih baik.

Jika kami mencetak baris saat ini tanpa baris baru dan mencetak baris baru hanya ketika baris berikutnya ada, kami memproses satu baris sekaligus dan baris terakhir tidak akan memiliki baris tambahan:

awk 'NR == 1 {printf ("% s", $ 0); selanjutnya}; {printf ("\ n% s", $ 0)} '

Atau, ditulis dengan cara lain:

awk 'NR>1{ print "" }; { printf( "%s", $0 ) }'

Atau:

awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'

Begitu:

$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
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.