Saya memiliki file yang cukup besar (35Gb), dan saya ingin memfilter file ini di situ (yaitu saya tidak memiliki cukup ruang disk untuk file lain), khususnya saya ingin menangkap dan mengabaikan beberapa pola - apakah ada cara untuk lakukan ini tanpa menggunakan file lain?
Katakanlah saya ingin memfilter semua baris yang berisi foo:
misalnya ...
Jawaban:
Pada tingkat panggilan sistem, ini harus dimungkinkan. Suatu program dapat membuka file target Anda untuk ditulis tanpa memotongnya dan mulai menulis apa yang dibaca dari stdin. Saat membaca EOF, file output dapat dipotong.
Karena Anda memfilter baris dari input, posisi penulisan file output harus selalu kurang dari posisi baca. Ini berarti Anda tidak boleh merusak input Anda dengan output baru.
Namun, menemukan program yang melakukan ini adalah masalahnya.
dd(1)
memiliki opsiconv=notrunc
yang tidak memotong file output saat terbuka, tetapi juga tidak memotong pada akhirnya, meninggalkan konten file asli setelah konten grep (dengan perintah sepertigrep pattern bigfile | dd of=bigfile conv=notrunc
)Karena sangat sederhana dari perspektif system call, saya menulis sebuah program kecil dan mengujinya pada sistem loopback file penuh kecil (1MiB). Itu melakukan apa yang Anda inginkan, tetapi Anda benar-benar ingin menguji ini dengan beberapa file lain terlebih dahulu. Itu selalu akan berisiko menimpa file.
menimpa.c
Anda akan menggunakannya sebagai:
Saya kebanyakan memposting ini untuk dikomentari oleh orang lain sebelum Anda mencobanya. Mungkin orang lain tahu tentang program yang melakukan hal serupa yang lebih teruji.
sumber
grep
tidak akan menghasilkan lebih banyak data daripada yang dibaca, posisi tulis harus selalu di belakang posisi baca. Bahkan jika Anda menulis dengan kecepatan yang sama seperti membaca, itu masih akan baik-baik saja. Coba rot13 dengan ini alih-alih grep, dan kemudian lagi. md5sum sebelum dan sesudah dan Anda akan melihat yang sama.dd
, tetapi tidak praktis.Anda dapat menggunakan
sed
untuk mengedit file di tempat (tapi ini memang membuat file sementara antara):Untuk menghapus semua baris yang mengandung
foo
:Untuk menjaga semua baris yang mengandung
foo
:sumber
$HOME
akan dapat ditulisi, tetapi/tmp
akan menjadi read-only (secara default). Misalnya, jika Anda memiliki Ubuntu dan Anda telah mem-boot ke Konsol Pemulihan, biasanya demikian. Juga, operator dokumen di sini<<<
tidak akan bekerja di sana juga, karena/tmp
harus r / w karena akan menulis file sementara ke sana juga. (lih. pertanyaan ini termasuk.strace
keluaran)Saya akan berasumsi bahwa perintah filter Anda adalah apa yang saya sebut filter prefix shrinking , yang memiliki properti yang byte N dalam output tidak pernah ditulis sebelum membaca setidaknya N byte input.
grep
memiliki properti ini (asalkan hanya memfilter dan tidak melakukan hal-hal lain seperti menambahkan nomor baris untuk kecocokan). Dengan filter semacam itu, Anda dapat menimpa input saat Anda melanjutkan. Tentu saja, Anda harus yakin untuk tidak membuat kesalahan, karena bagian yang ditimpa pada awal file akan hilang selamanya.Sebagian besar alat unix hanya memberikan pilihan untuk menambahkan file atau memotongnya, tanpa kemungkinan menimpanya. Satu-satunya pengecualian dalam kotak alat standar adalah
dd
, yang dapat dikatakan tidak memotong file outputnya. Jadi rencananya adalah menyaring perintah kedd conv=notrunc
. Ini tidak mengubah ukuran file, jadi kami juga mengambil panjang konten baru dan memotong file dengan panjang itu (lagi dengandd
). Perhatikan bahwa tugas ini pada dasarnya tidak kuat - jika terjadi kesalahan, Anda sendiri.Anda dapat menulis Perl kasar yang setara. Berikut ini adalah implementasi cepat yang tidak mencoba menjadi efisien. Tentu saja, Anda mungkin ingin melakukan pemfilteran awal secara langsung dalam bahasa itu juga.
sumber
Dengan shell seperti Bourne:
Untuk beberapa alasan, tampaknya orang cenderung melupakan operator pengalihan baca + tulis berusia 40 tahun dan standar .
Kami membuka
bigfile
di baca modus + write dan (apa yang paling penting di sini) tanpa pemotongan padastdout
saatbigfile
terbuka (secara terpisah) padacat
'sstdin
. Setelahgrep
dihentikan, dan jika telah menghapus beberapa baris,stdout
sekarang menunjuk ke suatu tempat di dalambigfile
, kita perlu menyingkirkan apa yang melampaui titik ini. Oleh karena ituperl
perintah yang memotong file (truncate STDOUT
) pada posisi saat ini (seperti yang dikembalikan olehtell STDOUT
).(
cat
adalah untuk GNUgrep
yang sebaliknya mengeluh jika stdin dan stdout menunjuk ke file yang sama).¹ Ya, meski
<>
sudah ada di kulit Bourne sejak awal di akhir tahun tujuh puluhan, awalnya tidak berdokumen dan tidak diimplementasikan dengan baik . Itu bukan dalam implementasi asliash
dari tahun 1989 dan, sementara itu adalahsh
operator pengalihan POSIX (sejak awal 90-an sebagai POSIXsh
didasarkan padaksh88
yang selalu memilikinya), itu tidak ditambahkan ke FreeBSDsh
misalnya sampai tahun 2000, jadi 15 tahun lama mungkin lebih akurat. Perhatikan juga bahwa deskriptor file default ketika tidak ditentukan ada<>
di semua shell, kecuali bahwa diksh93
dalamnya berubah dari 0 menjadi 1 di ksh93t + pada 2010 (melanggar kompatibilitas ke belakang dan kepatuhan POSIX)sumber
perl -e 'truncate STDOUT, tell STDOUT'
? Ini bekerja untuk saya tanpa memasukkannya. Adakah cara untuk mencapai hal yang sama tanpa menggunakan Perl?redirection "<>" fixed and documented (used in /etc/inittab f.i.).
yang merupakan salah satu petunjuk.Meskipun ini adalah pertanyaan lama, menurut saya ini adalah pertanyaan abadi, dan solusi yang lebih umum, lebih jelas tersedia daripada yang telah dikemukakan sejauh ini. Kredit di mana kredit jatuh tempo: Saya tidak yakin saya akan memunculkannya tanpa mempertimbangkan Stéphane Chazelas tentang
<>
operator pembaruan.Membuka file untuk pembaruan dalam shell Bourne adalah utilitas terbatas. Shell tidak memberi Anda cara untuk mencari pada file, dan tidak ada cara untuk mengatur panjang baru (jika lebih pendek dari yang lama). Tapi itu mudah diatasi, jadi mudah saya terkejut itu bukan salah satu utilitas standar di
/usr/bin
.Ini bekerja:
Seperti halnya ini (ujung topi ke Stéphane):
(Saya menggunakan GNU grep. Mungkin ada yang berubah sejak dia menulis jawabannya.)
Kecuali, Anda tidak memiliki / usr / bin / ftruncate . Untuk beberapa lusin baris C, Anda bisa, lihat di bawah. Utilitas funcuncate ini memotong deskriptor file sewenang-wenang hingga panjang sewenang-wenang, default ke output standar dan posisi saat ini.
Perintah di atas (contoh 1)
T
untuk pembaruan. Sama seperti dengan open (2), membuka file dengan cara ini menempatkan offset saat ini pada 0.T
normal, dan shell mengarahkan ulang outputnya keT
melalui deskriptor 4.Subshell kemudian keluar, deskriptor penutup 4. Berikut ini ftruncate :
NB, ftruncate (2) tidak dapat diport ketika digunakan dengan cara ini. Untuk generalitas absolut, baca byte tertulis terakhir, buka kembali file O_WRONLY, cari, tulis byte, dan tutup.
Mengingat bahwa pertanyaannya adalah 5 tahun, saya akan mengatakan solusi ini tidak jelas. Dibutuhkan dari eksekutif untuk membuka deskriptor baru, dan
<>
operator, yang keduanya misterius. Saya tidak bisa memikirkan utilitas standar yang memanipulasi inode oleh deskriptor file. (Sintaksnya bisaftruncate >&4
, tapi saya tidak yakin itu perbaikan.) Jauh lebih pendek dari jawaban yang kompeten dan kompeten camh. Itu hanya sedikit lebih jelas daripada Stéphane, IMO, kecuali jika Anda lebih suka Perl daripada saya. Saya harap seseorang menemukannya bermanfaat.Cara berbeda untuk melakukan hal yang sama adalah versi lseek (2) yang dapat dieksekusi yang melaporkan offset saat ini; outputnya bisa digunakan untuk / usr / bin / truncate , yang disediakan oleh beberapa Linuxi.
sumber
ed
mungkin merupakan pilihan yang tepat untuk mengedit file di tempat:sumber
ed
versi yang berbeda berperilaku berbeda ..... ini dariman ed
(GNU Ed 1.4) ...If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
ed
bukan solusi gool untuk mengedit file 35GB karena file tersebut dibaca ke dalam buffer.!
), jadi mungkin ada beberapa trik yang lebih menarik di lengannyaed
memotong file dan menulis ulang. Jadi ini tidak akan mengubah data pada disk di tempat sesuai keinginan OP. Selain itu, tidak dapat berfungsi jika file terlalu besar untuk dimuat dalam memori.Anda dapat menggunakan deskriptor file bash baca / tulis untuk membuka file Anda (untuk menimpanya di tempat), lalu
sed
dantruncate
... tetapi tentu saja, jangan pernah izinkan perubahan Anda menjadi lebih besar dari jumlah data yang dibaca sejauh ini .Ini skripnya (using: bash variable $ BASHPID)
Ini adalah hasil tes
sumber
Saya akan memetakan file-memori, melakukan semuanya di tempat menggunakan char * pointer ke memori telanjang, kemudian menghapus peta file dan memotongnya.
sumber
Tidak persis in-situ tetapi - ini bisa digunakan dalam keadaan yang serupa.
Jika ruang disk adalah masalah, kompres file terlebih dahulu (karena ini adalah teks, ini akan memberikan pengurangan besar) kemudian gunakan sed (atau grep, atau apa pun) dengan cara biasa di tengah-tengah pipa kompres / kompres.
sumber
sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
Demi siapa pun yang menelusuri pertanyaan ini, jawaban yang benar adalah berhenti mencari fitur shell yang tidak jelas yang berisiko merusak file Anda untuk mendapatkan peningkatan kinerja yang dapat diabaikan, dan alih-alih gunakan beberapa variasi dari pola ini:
Hanya dalam situasi yang sangat tidak umum bahwa ini karena suatu alasan tidak layak, sebaiknya Anda dengan serius mempertimbangkan jawaban lain di halaman ini (walaupun mereka tentu menarik untuk dibaca). Saya akan mengakui bahwa teka-teki OP karena tidak memiliki ruang disk untuk membuat file kedua persis situasi seperti itu. Meskipun demikian, ada opsi lain yang tersedia, misalnya seperti yang disediakan oleh @Ed Randall dan @Basile Starynkevitch.
sumber
echo -e "$(grep pattern bigfile)" >bigfile
sumber
grepped
data melebihi panjang dari apa yang diperbolehkan commandline. kemudian merusak data