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.)
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:
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
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.
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:
chomp
, karenachomp
hanya menghapus paling banyak satu trailing newline.chomp
adalah solusi awk yang sudah diposting LatinSuD. Tetapi dalam banyak kasuschomp
hanyalah alat untuk melakukan pekerjaan, dan saya menyediakan cara untuk melakukan beberapa tugas umum. Biarkan saya memperbarui jawaban saya untuk memperjelas ini.perl
Pendekatan lain . Yang ini membaca seluruh input ke dalam memori sehingga mungkin bukan ide yang baik untuk sejumlah besar data (gunakan cuonglm atauawk
pendekatan untuk itu):sumber
Saya mengambil ini dari repo github di suatu tempat, tetapi tidak dapat menemukan di mana
delete-trailing-blank-lines-sed
sumber
abstrak
Cetak baris tanpa baris baru, tambahkan baris baru hanya jika ada baris lain untuk dicetak.
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:
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
-z
opsi:Dengan awk (sembarang awk), hirup seluruh aliran, dan
printf
tanpa baris baru.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:
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:
Atau:
Begitu:
sumber