매우 큰 텍스트 파일의 마지막 두 줄을 효율적으로 제거


31

매우 큰 파일 (~ 400GB)이 있으며 마지막 2 줄을 제거해야합니다. 나는을 사용하려고했지만 sed포기하기 전에 몇 시간 동안 달렸다. 이 작업을 수행하는 빠른 방법이 sed있습니까 , 아니면 붙어 있습니까?


6
GNU 헤드를 사용해보십시오. head -n -2 file
user31894

답변:


31

나는 그것이 얼마나 빠른지 알기 위해 큰 파일에서 이것을 시도하지는 않았지만 상당히 빠릅니다.

스크립트를 사용하여 파일 끝에서 줄을 제거하려면

./shorten.py 2 large_file.txt

파일의 끝을 찾고 마지막 문자가 줄 바꿈인지 확인한 다음 세 줄 바꿈이 발견 될 때까지 한 번에 하나씩 각 문자를 읽고 해당 지점 바로 뒤에 파일을 자릅니다. 변경이 이루어졌습니다.

편집 : 맨 아래에 Python 2.4 버전을 추가했습니다.

다음은 Python 2.5 / 2.6 버전입니다.

#!/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)

다음은 Python 3 버전입니다.

#!/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)

다음은 Python 2.4 버전입니다.

#!/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)

우리의 시스템은 python 2.4를 실행하고 있으며, 우리의 서비스 중 하나가 그것에 의존하는지 확실하지 않습니다.
Russ Bradberry

@Russ : Python 2.4 용 버전을 추가했습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

1
정말 대단해! 매력처럼 그리고 1 초 안에 일했습니다!
Russ Bradberry

12

당신은 GNU 헤드를 시도 할 수 있습니다

head -n -2 file

단순하기 때문에 최상의 솔루션입니다.
xiao

1
이것은 심지어 내 시스템에서 작동하지 않습니다 자신의 file..an에서 제거 그에게 파일의 마지막 두 행을 보여 주지만되지 않습니다head: illegal line count -- -2
SooDesuNe

2
@SooDesuNe : 아니오 매뉴얼에 따라 처음부터 끝까지 2 줄까지 모든 줄을 인쇄합니다. 그러나이 파일을 파일로 리디렉션해야합니다. 그러면이 파일의 크기에 문제가 있으므로이 문제에 대한 완벽한 솔루션이 아닙니다.
Daniel Andersson

+1 왜 이것이 정답으로 받아 들여지지 않습니까? 빠르고 간단하며 예상대로 작동합니다.
aefxx

6
@ PetrMarek 및 기타 : 문제는 거대한 파일과 관련이 있다는 것 입니다. 이 솔루션을 사용하려면 전체 파일을 파이프를 통해 공급하고 모든 데이터를 새 위치에 다시 작성해야합니다. 문제의 핵심은이를 피하는 것입니다. 수락 된 답변의 솔루션과 같은 적절한 솔루션이 필요합니다.
Daniel Andersson

7

데비안 스퀴즈 / 테스트 시스템 (Lenny / stable 제외)에는 "coreutils"패키지의 일부로 "truncate"명령이 포함되어 있습니다.

그것으로 당신은 단순히 같은 것을 할 수 있습니다

truncate --size=-160 myfile

파일 끝에서 160 바이트를 제거하려면 (제거 해야하는 문자 수를 정확히 파악해야합니다).


이것은 파일을 제자리에서 수정하기 때문에 가장 빠른 경로이므로 파일을 복사하거나 파싱 할 필요가 없습니다. 그러나 여전히 제거 할 바이트 수를 확인해야합니다 ... 간단한 dd스크립트가 수행 할 I / guess / (마지막 킬로바이트를 얻은 다음 tail -2 | LANG= wc -c, 또는 sth 를 사용하려면 입력 오프셋을 지정해야 합니다).
liori

CentOS를 사용하고 있으므로 잘리지 않습니다. 그러나 이것이 바로 내가 찾는 것입니다.
Russ Bradberry

tail큰 파일에도 효율적입니다 tail | wc -c. 트리밍 할 바이트 수를 계산 하는 데 사용할 수 있습니다 .
krlmlr

6

sed의 문제점은 스트림 편집기라는 것입니다. 끝 부분 만 수정하고 싶더라도 전체 파일을 처리합니다. 따라서 무엇이든 상관없이 새로운 400GB 파일을 한 줄씩 작성합니다. 전체 파일에서 작동하는 편집기에는 아마도이 문제가있을 것입니다.

줄 수를 알고 있다면을 사용할 수 head있지만 다시 기존 파일을 변경하는 대신 새 파일을 만듭니다. 작업의 단순성으로 속도가 향상 될 수 있습니다.

당신은 수도 사용하여 더 나은 운이 split사용 후 마지막 편집하고, 작은 조각으로 파일을 깰 cat다시 결합하지만 더 나은 될 것입니다 있는지 확실하지 않습니다. 줄보다는 바이트 수를 사용합니다. 그렇지 않으면 전혀 빠르지 않을 것입니다-여전히 새로운 400GB 파일을 만들 것입니다.


2

VIM을 사용해보십시오 ... 큰 파일에 사용한 적이 없기 때문에 트릭을 수행할지 확실하지 않지만 과거에는 더 작은 파일에 사용했지만 시도해 보았습니다.


vim은 편집 할 때 버퍼 주위에 즉시로드되는 것을로드한다고 생각 하지만 어떻게 저장하는지 모르겠습니다.
Phoshi

파일을로드하려고 시도하는 동안 vim이 정지됨
Russ Bradberry

글쎄요, 아 기다려요. 로딩을 시작하고, 출근하고, 집에 돌아와서 완료되었는지 확인하십시오.
leeand00


1

어떤 종류의 파일이며 어떤 형식입니까? 텍스트, 그래픽, 바이너리 등의 파일 종류에 따라 Perl과 같은 것을 사용하는 것이 더 쉬울 수 있습니다. CSV, TSV ... 형식은 어떻게됩니까?


그것은 파이프 delimeted 텍스트 형식이지만, 마지막 두 줄은 각각 하나의 열이 내 수입을 깰 것이므로 제거해야합니다
Russ Bradberry

이 사건을 처리하기 위해 "가져 오기"가 무엇이든 수정하고 있습니까?
timday

가져 오기는 infobright의 "데이터 파일로드"가 아닙니다
Russ Bradberry

1

파일 크기를 바이트 (400000000160 say)로 알고 마지막 두 줄을 제거하기 위해 정확히 160자를 제거해야한다는 것을 알고 있다면

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

트릭을해야합니다. dd를 화나게 사용한 지 오래되었습니다. 더 큰 블록 크기를 사용하면 상황이 더 빨라진다는 것을 기억하는 것 같습니다. 그러나 그렇게 할 수 있는지 여부는 놓을 선이 좋은 배수인지 여부에 따라 다릅니다.

dd에는 텍스트 레코드를 고정 크기로 채우는 예비 옵션으로 유용 할 수있는 다른 옵션이 있습니다.


나는 이것을 시도했지만 sed와 같은 속도로 가고 있었다. 10 분 동안 약 200MB를 작성했으며이 속도로 완료하는 데 문자 그대로 수백 시간이 걸립니다.
Russ Bradberry

1

시스템에서 "truncate"명령을 사용할 수없는 경우 (다른 답변 참조) 시스템 호출에 대한 "man 2 truncate"에서 파일을 지정된 길이로 자릅니다.

분명히 파일을 자르는 데 필요한 문자 수를 알아야합니다 (크기에서 두 줄의 문제를 뺀 두 줄; cr / lf 문자를 세는 것을 잊지 마십시오).

그리고 시도하기 전에 파일을 백업하십시오!


1

유닉스 스타일 솔루션을 선호하는 경우 세 줄의 코드 (Mac 및 Linux에서 테스트 됨)를 사용하여 저장 및 대화식 줄 잘림을 수행 할 수 있습니다.

작고 안전한 유닉스 스타일의 줄 잘림 (확인 요청) :

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` )"

이 솔루션은 몇 가지 일반적인 유닉스 도구를 사용하지만 여전히 모든 시스템에서 사용할 수있는 perl -e "truncate(file,length)"가장 가까운 대체 도구로 사용 truncate(1)합니다.

사용 정보를 제공하고 잘림 확인, 옵션 구문 분석 및 오류 처리 기능을 제공하는 다음과 같은 포괄적 인 재사용 가능한 셸 프로그램을 사용할 수도 있습니다.

포괄적 인 줄 잘림 스크립트 :

#!/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

사용 예는 다음과 같습니다.

$ 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

에드 "$ 1"<< 여기
$
디
디
승
이리

변경이 이루어집니다. 이것은 파이썬 스크립트보다 간단하고 효율적입니다.


내 시스템에서 백만 줄과 57MB가 넘는 텍스트 파일을 사용하면 edPython 스크립트보다 실행 시간이 100 배 길었 습니다. OP의 파일의 차이가 7000 배 더 큰 차이 만 상상할 수 있습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

0

유사한 문제를 해결하기 위해 허용 된 답변을 수정했습니다. n 줄을 제거하기 위해 약간 조정될 수 있습니다.

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

그리고 해당 테스트 :

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

Ex 모드에서 Vim을 사용할 수 있습니다 :

ex -sc '-,d|x' file
  1. -, 마지막 두 줄을 선택하십시오

  2. d 지우다

  3. x 저장하고 닫습니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.