Eu tenho um arquivo muito grande (~ 400 GB) e preciso remover as duas últimas linhas dele. Eu tentei usar sed
, mas funcionou por horas antes de desistir. Existe uma maneira rápida de fazer isso, ou eu estou preso sed
?
Eu tenho um arquivo muito grande (~ 400 GB) e preciso remover as duas últimas linhas dele. Eu tentei usar sed
, mas funcionou por horas antes de desistir. Existe uma maneira rápida de fazer isso, ou eu estou preso sed
?
Respostas:
Eu não tentei isso em um arquivo grande para ver o quão rápido é, mas deve ser bastante rápido.
Para usar o script para remover linhas do final de um arquivo:
./shorten.py 2 large_file.txt
Ele procura o final do arquivo, verifica se o último caractere é uma nova linha, depois lê cada caractere um de cada vez, retrocedendo até encontrar três novas linhas e truncar o arquivo logo após esse ponto. A alteração é feita no local.
Edit: Adicionei uma versão do Python 2.4 na parte inferior.
Aqui está uma versão do 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)
Aqui está uma versão do 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)
Aqui está uma versão do 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)
você pode tentar o GNU head
head -n -2 file
head: illegal line count -- -2
Eu vejo que meus sistemas Debian Squeeze / testing (mas não o Lenny / stable) incluem um comando "truncate" como parte do pacote "coreutils".
Com isso, você poderia simplesmente fazer algo como
truncate --size=-160 myfile
para remover 160 bytes do final do arquivo (obviamente você precisa descobrir exatamente quantos caracteres você precisa remover).
dd
serão removidos ... Acho que um script simples fará isso (você precisa especificar o deslocamento da entrada para obter o último kilobyte fe e depois usá-lo tail -2 | LANG= wc -c
ou sth assim).
tail
também é eficiente para arquivos grandes - pode ser usado tail | wc -c
para calcular o número de bytes a serem aparados.
O problema com o sed é que ele é um editor de fluxo - ele processará o arquivo inteiro, mesmo que você queira fazer modificações apenas no final. Então, não importa o quê, você está criando um novo arquivo de 400 GB, linha por linha. Qualquer editor que opere no arquivo inteiro provavelmente terá esse problema.
Se você souber o número de linhas, poderá usar head
, mas novamente isso cria um novo arquivo em vez de alterar o existente. Você pode obter ganhos de velocidade com a simplicidade da ação, eu acho.
Você pode ter mais sorte usando split
para quebrar o arquivo em pedaços menores, editando o último e, em seguida, usando-os cat
para combiná-los novamente, mas não tenho certeza se será melhor. Eu usaria contagens de bytes em vez de linhas, caso contrário provavelmente não será mais rápido - você ainda estará criando um novo arquivo de 400 GB.
Experimente o VIM ... Não tenho certeza se isso funcionará ou não, pois nunca o usei em um arquivo tão grande, mas o usei em arquivos maiores menores no passado, tente.
Que tipo de arquivo e em qual formato? Pode ser mais fácil usar algo como Perl, dependendo do tipo de arquivo - texto, gráficos, binário? Como é formatado - CSV, TSV ...
Se você sabe o tamanho do arquivo no byte (digamos 400000000160) e sabe que precisa remover exatamente 160 caracteres para remover as duas últimas linhas, algo como
dd if=originalfile of=truncatedfile ibs=1 count=400000000000
deve fazer o truque. Já faz muito tempo desde que eu usei dd com raiva; Parece que lembro que as coisas ficam mais rápidas se você usar um tamanho de bloco maior, mas se você pode fazer isso depende se as linhas que você deseja soltar estão em um bom múltiplo.
O dd tem outras opções para preencher os registros de texto em um tamanho fixo, o que pode ser útil como passe preliminar.
Se o comando "truncar" não estiver disponível no seu sistema (veja minha outra resposta), consulte o "man 2 truncar" da chamada do sistema para truncar um arquivo com um comprimento especificado.
Obviamente, você precisa saber quantos caracteres precisa truncar o arquivo (tamanho menos o comprimento do problema em duas linhas; não se esqueça de contar caracteres cr / lf).
E faça um backup do arquivo antes de tentar isso!
Se você preferir soluções no estilo unix, poderá ter truncamento de linha interativo e salvo usando três linhas de código (Testado no Mac e Linux).
pequeno + truncamento de linha no estilo unix seguro (solicita confirmação):
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` )"
Essa solução conta com algumas ferramentas unix comuns, mas ainda é usada perl -e "truncate(file,length)"
como substituta mais próxima truncate(1)
, o que não está disponível em todos os sistemas.
Você também pode usar o seguinte programa abrangente de shell reutilizável, que fornece informações de uso e apresenta confirmação de truncamento, análise de opções e tratamento de erros.
script de truncamento de linha abrangente :
#!/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
Aqui está um exemplo de uso:
$ 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
#! / bin / sh ed "$ 1" << AQUI $ d d W AQUI
mudanças são feitas no local. Isso é mais simples e mais eficiente que o script python.
ed
demorou 100 vezes mais tempo para ser executado do que o meu script Python. Posso apenas imaginar quanto mais a diferença seria para o arquivo do OP, que é 7000 vezes maior.
Modificou a resposta aceita para resolver um problema semelhante. Pode ser ajustado um pouco para remover n linhas.
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
E o teste correspondente:
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()
Você pode usar o Vim no modo Ex:
ex -sc '-,d|x' file
-,
selecione as últimas 2 linhas
d
excluir
x
salvar e fechar
head -n -2 file