Tambahkan baris ke awal dan akhir file besar

23

Saya punya skenario di mana baris ditambahkan pada awal dan akhir file besar.

Saya sudah mencoba seperti yang ditunjukkan di bawah ini.

  • untuk baris pertama:

    sed -i '1i\'"$FirstLine" $Filename
  • untuk baris terakhir:

    sed -i '$ a\'"$Lastline" $Filename  

Tetapi masalah dengan perintah ini adalah bahwa ia menambahkan baris pertama file dan melintasi seluruh file. Untuk baris terakhir itu lagi melintasi seluruh file dan menambahkan baris terakhir. Karena file yang sangat besar (14GB) ini membutuhkan waktu yang sangat lama.

Bagaimana saya bisa menambahkan baris ke awal dan yang lain ke akhir file sambil hanya membaca file sekali?

UNIXbest
sumber

Jawaban:

20

sed -imenggunakan tempfile sebagai detail implementasi, yang Anda alami; Namun, menambahkan data ke awal aliran data tanpa menimpa konten yang ada membutuhkan penulisan ulang file, tidak ada cara untuk menyiasatinya, bahkan ketika menghindarinya sed -i.

Jika menulis ulang file bukan opsi, Anda dapat mempertimbangkan untuk memanipulasinya ketika dibaca, misalnya:

{ echo some prepended text ; cat file ; } | command

Juga, sed adalah untuk mengedit aliran - file bukan aliran. Gunakan program yang dimaksudkan untuk tujuan ini, seperti ed atau ex. The -ipilihan untuk sed tidak hanya tidak portabel, itu juga akan merusak symlink ke file Anda, karena pada dasarnya menghapus dan recreates itu, yang sia-sia.

Anda dapat melakukan ini dalam satu perintah dengan edseperti:

ed -s file << 'EOF'
0a
prepend these lines
to the beginning
.
$a
append these lines
to the end
.
w
EOF

Perhatikan bahwa tergantung pada implementasi ed Anda, itu mungkin menggunakan file paging, mengharuskan Anda untuk memiliki setidaknya banyak ruang yang tersedia.

Chris Down
sumber
Hai, ed perintah yang Anda berikan berfungsi dengan sangat baik untuk file besar. Tapi saya punya 3 file besar seperti Test, Test1, Test 2. Saya memberi perintah seperti ed -s Tes * << 'EOF' 0a tambahkan baris ini ke awal. $ a tambahkan baris-baris ini sampai akhir. w EOF Tapi hanya mengambil file Test dan menambahkan baris pertama / terakhir. Bagaimana kita dapat membuat perubahan dalam perintah yang sama sehingga harus melakukan penambahan baris pertama dan terakhir di semua file.
UNIXbest
@UNIXbest - Gunakan forperulangan:for file in Tes*; do [command]; done
Chris Down
Hai Bawah, saya telah menggunakan perintah di bawah untuk file di Tes *; lakukan ed -s Tes * << 'EOF' 0a HEllO HDR. $ a Halo TLR. w EOF selesai Tapi masih menulis ke file pertama.
UNIXbest
Benar, karena Anda perlu menggunakan "$file", bukan Tes*sebagai argumen ed.
Chris Down
2
@ UNIXbest Jika masalah Anda telah dipecahkan oleh jawaban ini, Anda harus mempertimbangkan menerimanya.
Joseph R.
9

Perhatikan bahwa jika Anda ingin menghindari mengalokasikan seluruh salinan file pada disk, Anda dapat melakukan:

sed '
1i\
begin
$a\
end' < file 1<> file

Itu menggunakan fakta bahwa ketika stdin / stdout adalah file, sed membaca dan menulis dengan blok. Jadi di sini, sedboleh saja untuk mengganti file yang dibacanya selama baris pertama yang Anda tambahkan lebih kecil dari ukuran blok (harus sekitar 4k atau 8k).

Perhatikan bahwa jika karena alasan tertentu sedgagal (terbunuh, kerusakan mesin ...), Anda akan berakhir dengan file setengah diproses yang berarti sejumlah data ukuran baris pertama hilang di suatu tempat di tengah.

Perhatikan juga bahwa kecuali Anda sedadalah GNU sed, itu tidak akan berfungsi untuk data biner (tetapi karena Anda menggunakan -i, Anda menggunakan sed GNU).

Stéphane Chazelas
sumber
kesalahan ini bagi saya di Ubuntu 16.04
Csaba Toth
4

Berikut adalah beberapa pilihan (semuanya akan membuat salinan file baru jadi pastikan Anda memiliki cukup ruang untuk itu):

  • gema / kucing sederhana

    echo "first" > new_file; cat $File >> new_file; \
      echo "last" >> new_file; 
  • awk / gawk dll

    gawk 'BEGIN{print "first\n"}{print}END{print "last\n"}' $File > NewFile 

    awkdan sejenisnya membaca file baris demi baris. The BEGIN{}blok dijalankan sebelum baris pertama dan END{}blok setelah baris terakhir. Jadi, perintah di atas berarti print "first" at the beginning, then print every line in the file and print "last" at the end.

  • Perl

    perl -ne 'BEGIN{print "first\n"} print;END{print "last\n"}' $File > NewFile

    Ini pada dasarnya hal yang sama dengan gawk di atas yang baru saja ditulis dalam Perl.

terdon
sumber
1
Perhatikan bahwa dalam semua kasus ini, Anda akan memerlukan setidaknya 14GB lebih banyak ruang untuk file baru.
Chris Down
@ChrisDown poin bagus, saya mengedit jawaban saya untuk memperjelasnya. Saya berasumsi itu bukan masalah karena OP menggunakan sed -iyang membuat file temp.
terdon
3

Saya lebih suka yang lebih sederhana:

gsed -i '1s/^/foo\n/gm; $s/$/\nbar/gm' filename.txt

Ini mengubah file:

asdf
qwer

ke file:

foo
asdf
qwer
bar
CommaToast
sumber
2

Anda dapat menggunakan Vim dalam mode Ex:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 pilih baris pertama

  2. i masukkan teks dan baris baru

  3. $ pilih baris terakhir

  4. a tambahkan teks dan baris baru

  5. x Simpan dan tutup

Steven Penny
sumber
bagaimana jika kita ingin melakukan ini pada banyak file?
geoyws
1
@geoyws itu tidak benar-benar dalam ruang lingkup untuk pertanyaan ini
Steven Penny
Anda yakin itu $ a dan bukan% a?
Carlos Robles
2

Tidak ada cara untuk memasukkan data di awal file¹, yang dapat Anda lakukan adalah membuat file baru, menulis data tambahan, dan menambahkan data lama. Jadi, Anda harus menulis ulang seluruh file setidaknya satu kali untuk memasukkan baris pertama. Anda dapat menambahkan baris terakhir tanpa menulis ulang file.

sed -i '1i\'"$FirstLine" $Filename
echo "$LastLine" >>$Filename

Atau, Anda dapat menggabungkan dua perintah dalam satu kali sed.

sed -i -e '1i\'"$FirstLine" -e '$ a\'"$Lastline" $Filename

sed -imembuat file output baru dan kemudian memindahkannya ke file lama. Ini berarti bahwa ketika sed sedang bekerja, ada salinan kedua file menggunakan ruang kosong. Anda dapat menghindari ini dengan menimpa file di tempat , tetapi dengan batasan besar: baris yang Anda tambahkan harus lebih kecil dari buffer sed, dan jika sistem Anda macet Anda akan berakhir dengan file yang rusak dan beberapa konten hilang di tengah, jadi saya sangat merekomendasikan untuk tidak melakukannya.

¹ Linux memang memiliki cara untuk memasukkan data ke dalam file, tetapi ia hanya dapat menyisipkan sejumlah besar blok sistem file, ia tidak dapat memasukkan string dengan panjang sewenang-wenang. Ini berguna untuk beberapa aplikasi, seperti database dan mesin virtual, tetapi tidak berguna untuk file teks.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Tidak benar. Lihatlah fallocate()dengan FALLOC_FL_INSERT_RANGEtersedia di XFS dan ext4 di kernel modern (4.xx) man7.org/linux/man-pages/man2/fallocate.2.html
Eric
@ Eric Anda hanya bisa memasukkan seluruh blok, tidak dengan panjang byte acak, setidaknya pada Linux 4.15.0 dengan ext4. Apakah ada sistem file yang dapat memasukkan panjang byte acak?
Gilles 'SANGAT berhenti menjadi jahat'
Benar tapi tetap tidak membuat pernyataan Anda benar. Anda menulis: "Tidak ada cara untuk memasukkan data di awal file". Itu masih tidak benar: ada mekanisme untuk memasukkan luasan di awal file. Itu datang dengan peringatan, tentu saja, tetapi perlu disebutkan karena beberapa pengguna mungkin tidak peduli tentang pembatasan ukuran blok dengan mengisi spasi atau pengembalian carriage.
Eric
0
$ (echo "Some Text" ; cat file1) > file2
Koushik Karmakar
sumber
4
Hanya jawaban kode yang tidak dapat diterima, harap tingkatkan jawaban Anda
Networker
Pertimbangkan memperluas jawaban Anda untuk memasukkan penjelasan tentang saran Anda, atau tautan ke dokumentasi yang mendukung solusi Anda.
HalosGhost
-1

Kernel Linux modern (lebih tinggi dari 4,1 atau 4,2) mendukung memasukkan data pada awal file melalui fallocate()system call dengan FALLOC_FL_INSERT_RANGEpada sistem file ext4 dan xfs. Pada dasarnya ini adalah operasi pemindahan logis: data secara logis dipindahkan pada offset yang lebih tinggi.

Ada kendala terkait granularity dari rentang yang ingin Anda masukkan di awal file. Tetapi untuk file teks Anda mungkin dapat mengalokasikan sedikit lebih dari yang dibutuhkan (hingga batas granularity) dan mengisi dengan spasi atau carriage return, tetapi itu tergantung pada aplikasi Anda

Saya tidak tahu ada utilitas linux yang tersedia yang memanipulasi luasan file tetapi tidak sulit untuk menulis: dapatkan deskriptor file dan panggil fallocate()dengan argumen yang sesuai. Untuk perincian lebih lanjut, lihat halaman manual dari fallocatepanggilan sistem: http://man7.org/linux/man-pages/man2/fallocate.2.html

Eric
sumber
Utilitas bukanlah masalah (dengan asumsi Linux yang tidak tertanam): util-linux mengandung fallocateutilitas. Masalahnya adalah bahwa rincian seluruh blok membuat ini tidak berguna untuk sebagian besar file teks. Masalah lain adalah bahwa alokasi rentang dan modifikasi selanjutnya bukan atom. Jadi ini sebenarnya tidak menyelesaikan masalah di sini.
Gilles 'SO- stop being evil'
Granularity adalah peringatan yang sudah saya sebutkan dan tidak, itu tidak membuatnya tidak berguna, itu tergantung pada aplikasi. Di mana Anda melihat dalam pertanyaan bahwa atomicity itu penting? Saya hanya bisa melihat masalah penampilan. Meski begitu syscall ini tampaknya bersifat atomik: elixir.bootlin.com/linux/latest/source/fs/open.c#L228 dan jika atomisitas menjadi penting (tidak, tetapi katakan itu demi argumen) maka cukup gunakan penguncian file. (tunjukkan saya ke tempat di kode kernel di mana fallocateatomicity rusak tolong, saya ingin tahu)
Eric