Son derece büyük bir metin dosyasının son iki satırını verimli bir şekilde kaldırın


Yanıtlar:


31

Bunu ne kadar hızlı olduğunu görmek için büyük bir dosyada denemedim, ancak oldukça hızlı olması gerekiyor.

Bir dosyanın sonundan satırları kaldırmak için komut dosyasını kullanmak için:

./shorten.py 2 large_file.txt

Dosyanın sonuna bakar, son karakterin yeni bir satır olduğundan emin olmak için kontrol eder, ardından üç karakter satırı bulunana kadar her bir karakteri bir defa geriye doğru okur ve bu noktadan hemen sonra dosyayı keser. Değişiklik yapıldı.

Düzenleme: En altta bir Python 2.4 sürümü ekledim.

İşte Python 2.5 / 2.6 için bir sürüm:

#!/usr/bin/env python2.5
from __future__ import with_statement
# also tested with Python 2.6

import os, sys

if len(sys.argv) != 3:
    print sys.argv[0] + ": Invalid number of arguments."
    print "Usage: " + sys.argv[0] + " linecount filename"
    print "to remove linecount lines from the end of the file"
    exit(2)

number = int(sys.argv[1])
file = sys.argv[2]
count = 0

with open(file,'r+b') as f:
    f.seek(0, os.SEEK_END)
    end = f.tell()
    while f.tell() > 0:
        f.seek(-1, os.SEEK_CUR)
        char = f.read(1)
        if char != '\n' and f.tell() == end:
            print "No change: file does not end with a newline"
            exit(1)
        if char == '\n':
            count += 1
        if count == number + 1:
            f.truncate()
            print "Removed " + str(number) + " lines from end of file"
            exit(0)
        f.seek(-1, os.SEEK_CUR)

if count < number + 1:
    print "No change: requested removal would leave empty file"
    exit(3)

İşte bir Python 3 versiyonu:

#!/usr/bin/env python3.0

import os, sys

if len(sys.argv) != 3:
    print(sys.argv[0] + ": Invalid number of arguments.")
    print ("Usage: " + sys.argv[0] + " linecount filename")
    print ("to remove linecount lines from the end of the file")
    exit(2)

number = int(sys.argv[1])
file = sys.argv[2]
count = 0

with open(file,'r+b', buffering=0) as f:
    f.seek(0, os.SEEK_END)
    end = f.tell()
    while f.tell() > 0:
        f.seek(-1, os.SEEK_CUR)
        print(f.tell())
        char = f.read(1)
        if char != b'\n' and f.tell() == end:
            print ("No change: file does not end with a newline")
            exit(1)
        if char == b'\n':
            count += 1
        if count == number + 1:
            f.truncate()
            print ("Removed " + str(number) + " lines from end of file")
            exit(0)
        f.seek(-1, os.SEEK_CUR)

if count < number + 1:
    print("No change: requested removal would leave empty file")
    exit(3)

İşte bir Python 2.4 versiyonu:

#!/usr/bin/env python2.4

import sys

if len(sys.argv) != 3:
    print sys.argv[0] + ": Invalid number of arguments."
    print "Usage: " + sys.argv[0] + " linecount filename"
    print "to remove linecount lines from the end of the file"
    sys.exit(2)

number = int(sys.argv[1])
file = sys.argv[2]
count = 0
SEEK_CUR = 1
SEEK_END = 2

f = open(file,'r+b')
f.seek(0, SEEK_END)
end = f.tell()

while f.tell() > 0:
    f.seek(-1, SEEK_CUR)
    char = f.read(1)
    if char != '\n' and f.tell() == end:
        print "No change: file does not end with a newline"
        f.close()
        sys.exit(1)
    if char == '\n':
        count += 1
    if count == number + 1:
        f.truncate()
        print "Removed " + str(number) + " lines from end of file"
        f.close()
        sys.exit(0)
    f.seek(-1, SEEK_CUR)

if count < number + 1:
    print "No change: requested removal would leave empty file"
    f.close()
    sys.exit(3)

sistemimiz python 2.4 kullanıyor ve hizmetlerimizden herhangi birinin buna güvenip güvenmediğinden emin değilim, bu işe yarayacak mı?
Russ Bradberry

@Russ: Python 2.4 için bir sürüm ekledim.
sonraki duyuruya kadar duraklatıldı.

1
kesinlikle muhteşem! bir cazibe gibi çalıştı ve bir saniyeden daha kısa sürede!
Russ Bradberry

12

GNU kafasını deneyebilirsin

head -n -2 file

Basit olduğu için en iyi çözümdür.
xiao

1
Bu ona dosyanın son iki satırını gösterecek, ancak dosyalarından çıkarmayacak. Bir de sistemimde çalışmıyorhead: illegal line count -- -2
SooDesuNe

2
@SooDesuNe: Hayır, kılavuzun başından sonuna kadar tüm satırları baştan sonuna 2 satır yazdıracaktır. Bununla birlikte, bunun bir dosyaya yönlendirilmesi gerekir ve sonra bu dosyanın dev olmasıyla ilgili bir sorun vardır, bu nedenle bu sorun için mükemmel bir çözüm değildir.
Daniel Andersson

+1 Neden bu doğru cevap olarak kabul edilmiyor? Hızlı, basit ve beklendiği gibi çalışıyor.
aefxx

6
@PetrMarek ve diğerleri: Sorun devasa bir dosyayla ilgiliydi . Bu çözüm, dosyanın tamamının bir borudan beslenmesini ve tüm verilerin yeni bir yere yeniden yazılmasını gerektirir - ve sorunun asıl amacı bundan kaçınmaktır. Kabul edilen cevapta olduğu gibi yerinde bir çözüme ihtiyaç vardır.
Daniel Andersson

7

Debian Squeeze / Test sistemlerimin (fakat Lenny / stable değil) "coreutils" paketinin bir parçası olarak "truncate" komutunu içerdiğini görüyorum.

Bununla beraber sadece bir şey yapabilirdi

truncate --size=-160 myfile

Dosyanın sonundan 160 bayt kaldırmak için (açıkçası kaldırmanız gereken tam olarak kaç karakter bulmanız gerekiyor).


Bu, yerinde dosyayı değiştirdiği için en hızlı yol olacaktır ve bu nedenle dosyayı kopyalamak veya ayrıştırmak gerekmez. Bununla birlikte, kaç tane baytın kaldırılacağını hala kontrol etmeniz gerekir ... Basit bir ddbetiğin bunu yapacağını tahmin ediyorum tail -2 | LANG= wc -c.
liori

CentOS kullanıyorum, bu yüzden hayır, kısaltmam yok. Ancak, bu tam olarak aradığım şey.
Russ Bradberry

tailbüyük dosyalar için de etkilidir - tail | wc -ckırpılacak bayt sayısını hesaplamak için kullanılabilir.
krlmlr 19:16

6

Sed ile ilgili sorun bunun bir akış editörü olması - sadece sonuna kadar değişiklik yapmak isteseniz bile tüm dosyayı işleyecektir. Yani ne olursa olsun, satır satır satır yeni bir 400GB dosya oluşturuyorsunuz. Tüm dosya üzerinde çalışan herhangi bir editör muhtemelen bu soruna sahip olacaktır.

Satır sayısını biliyorsanız, kullanabilirsiniz head, ancak bu, mevcut olanı değiştirmek yerine yeni bir dosya oluşturur. Sanırım, eylemin basitliğinden hız kazanabilirsiniz.

Sen belki kullanarak daha şanslı splitkullanarak sonra sonuncuyu düzenleme ve küçük parçalar halinde dosya kırmak için catonları tekrar birleştirmek, ancak herhangi bir iyi olacak olmadığından emin değilim. Satırlar yerine bayt sayıları kullanırdım, aksi takdirde muhtemelen daha hızlı olmaz - hala yeni bir 400GB dosya oluşturacaksınız.


2

VIM'i deneyin ... Hile yapıp yapmayacağından emin değilim, çünkü bu kadar büyük bir dosyada hiç kullanmamıştım, ancak geçmişte daha küçük dosyalarda kullanmıştım.


Vim'in düzenleme sırasında hemen arabellek etrafındakileri yüklediğine inanıyorum , ancak nasıl tasarruf ettiği konusunda hiçbir fikrim yok.
Phoshi

vim dosyayı yüklemeye çalışırken askıda kalıyor
Russ Bradberry

Eğer takılırsa, ah bekleyin. Yüklemeye başla, işe git, eve gel, bak.
leeand00


1

Ne tür bir dosya ve hangi biçimde? Ne tür bir dosya olduğuna bağlı olarak Perl gibi bir şey kullanmak daha kolay olabilir mi - metin, grafik, ikili? Nasıl biçimlendirilir - CSV, TSV ...


metin boru biçiminde metin şeklinde biçimlendirilmiş, ancak son 2 satır, her biri içe aktarımı kıracak bir sütundur, bu nedenle bunların kaldırılmasını istiyorum
Russ Bradberry

bu durumda ele almak için "içe aktarma" neyi düzeltiyor?
timgün

no import infobright'ın "veri yükleme dosyası"
Russ Bradberry

1

Dosyanın boyutunu bayt olarak biliyorsanız (400000000160 diyelim) ve son iki satırı sıyırmak için tam olarak 160 karakteri silmeniz gerektiğini biliyorsanız,

dd if=originalfile of=truncatedfile ibs=1 count=400000000000

hile yapmalı. GG'yi öfkeyle kullandığımdan beri yıllar geçti; Daha büyük bir blok boyutu kullanıyorsanız, işlerin daha hızlı gittiğini hatırlıyor gibiyim, ancak bunu yapıp yapmamanız, bırakmak istediğiniz çizgilerin hoş bir katında olup olmadığına bağlı.

dd, metin kayıtlarını ön geçiş olarak yararlı olabilecek sabit bir boyuta yapıştırmak için başka seçeneklere de sahiptir.


Bunu denedim, ama sed ile aynı hızda gidiyordu. 10 dakikada yaklaşık 200 MB yazmıştı, bu oranda kelimenin tam anlamıyla yüzlerce saat alacaktı.
Russ Bradberry

1

"Truncate" komutu sisteminizde mevcut değilse (diğer cevabımı inceleyin), sistem çağrısının belirtilen uzunlukta bir dosyayı kesmesi için "man 2 truncate" konusuna bakın.

Açıkçası, dosyayı kısaltmanız gereken karakter sayısını bilmeniz gerekir (boyut eksi sorunun uzunluğu iki satır; herhangi bir cr / lf karakteri saymayı unutmayın).

Ve bunu denemeden önce dosyayı yedekleyin!


1

Unix tarzı çözümler tercih ederseniz, üç satır kod kullanarak (Mac ve Linux'ta test edilmiştir) kaydetme ve etkileşimli satır kesmeye sahip olabilirsiniz.

küçük + güvenli unix tarzı çizgi kesmesi (onay için sorar):

n=2; file=test.csv; tail -n $n $file &&
read -p "truncate? (y/N)" -n1 key && [ "$key" == "y" ] &&
perl -e "truncate('$file', `wc -c <$file` - `tail -n $n $file | wc -c` )"

Bu çözüm birkaç ortak unix-alete dayanmaktadır, ancak yine de tüm sistemlerde bulunmayan perl -e "truncate(file,length)"en yakın yerine truncate(1)kullanılmaktadır.

Kullanım bilgisi sağlayan ve kesme onayı, seçenek ayrıştırma ve hata yönetimi özelliklerine sahip aşağıdaki kapsamlı yeniden kullanılabilir kabuk programını da kullanabilirsiniz.

kapsamlı satır kesme komut dosyası :

#!/usr/bin/env bash

usage(){
cat <<-EOF
  Usage:   $0 [-n NUM] [-h] FILE
  Options:
  -n NUM      number of lines to remove (default:1) from end of FILE
  -h          show this help
EOF
exit 1
}

num=1

for opt in $*; do case $opt in
  -n) num=$2;                 shift;;
  -h) usage;                  break;;
  *)  [ -f "$1" ] && file=$1; shift;;
esac done

[ -f "$file" ] || usage

bytes=`wc -c <$file`
size=`tail -n $num $file | wc -c`

echo "using perl 'truncate' to remove last $size of $bytes bytes:"
tail -n $num $file
read -p "truncate these lines? (y/N)" -n1 key && [ "$key" == "y" ] &&
perl -e "truncate('$file', $bytes - $size )"; echo ""
echo "new tail is:"; tail $file

İşte bir kullanım örneği:

$ cat data/test.csv
1 nice data
2 cool data
3 just data

GARBAGE to be removed (incl. empty lines above and below)

$ ./rmtail.sh -n 3 data/test.csv
using perl 'truncate' to remove last 60 of 96 bytes:

GARBAGE to be removed (incl. empty lines above and below)

truncate these lines? (y/N)y
new tail is:
1 nice data
2 cool data
3 just data
$ cat data/test.csv
1 nice data
2 cool data
3 just data

0
#! / Bin / sh

ed "$ 1" << BURAYA
$
d
d
w
İŞTE

değişiklikler yapıldı. Bu python betiğinden daha basit ve daha etkilidir.


Sistemimde, bir milyon satırdan ve 57 MB'den büyük bir metin dosyası kullanarak edPython komut dizimden daha uzun sürdüm . OP'nin 7000 kat daha büyük olan dosyası için ne kadar fazla fark olacağını hayal edebiliyorum.
sonraki duyuruya kadar duraklatıldı.

0

Benzer bir sorunu çözmek için kabul edilen cevabı değiştirildi. N satırlarını kaldırmak için biraz tweaked olabilir.

import os

def clean_up_last_line(file_path):
    """
    cleanup last incomplete line from a file
    helps with an unclean shutdown of a program that appends to a file
    if \n is not the last character, remove the line
    """
    with open(file_path, 'r+b') as f:
        f.seek(0, os.SEEK_END)

        while f.tell() > 0: ## current position is greater than zero
            f.seek(-1, os.SEEK_CUR)

            if f.read(1) == '\n':
                f.truncate()
                break

            f.seek(-1, os.SEEK_CUR) ## don't quite understand why this has to be called again, but it doesn't work without it

Ve ilgili test:

import unittest

class CommonUtilsTest(unittest.TestCase):

    def test_clean_up_last_line(self):
        """
        remove the last incomplete line from a huge file
        a line is incomplete if it does not end with a line feed
        """
        file_path = '/tmp/test_remove_last_line.txt'

        def compare_output(file_path, file_data, expected_output):
            """
            run the same test on each input output pair
            """
            with open(file_path, 'w') as f:
                f.write(file_data)

            utils.clean_up_last_line(file_path)

            with open(file_path, 'r') as f:
                file_data = f.read()
                self.assertTrue(file_data == expected_output, file_data)        

        ## test a multiline file
        file_data = """1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b
1362358458954466,2013-03-03 16:54:18,34.5,3.0,b
1362358630923094,2013-03-03 16:57:10,34.5,50.0,b
136235"""

        expected_output = """1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b
1362358458954466,2013-03-03 16:54:18,34.5,3.0,b
1362358630923094,2013-03-03 16:57:10,34.5,50.0,b
"""        
        compare_output(file_path, file_data, expected_output)

        ## test a file with no line break
        file_data = u"""1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b"""
        expected_output = "1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b"
        compare_output(file_path, file_data, expected_output)

        ## test a file a leading line break
        file_data = u"""\n1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b"""
        expected_output = "\n"
        compare_output(file_path, file_data, expected_output)

        ## test a file with one line break
        file_data = u"""1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b\n""" 
        expected_output = """1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b\n""" 
        compare_output(file_path, file_data, expected_output)

        os.remove(file_path)


if __name__ == '__main__':
    unittest.main()

0

Vim'i Ex modunda kullanabilirsiniz:

ex -sc '-,d|x' file
  1. -, son 2 satırı seç

  2. d silmek

  3. x kaydet ve kapat

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.