Tengo un archivo muy grande (~ 400 GB) y necesito eliminar las últimas 2 líneas. Traté de usarlo sed
, pero funcionó durante horas antes de rendirme. ¿Hay alguna forma rápida de hacer esto o estoy atascado sed
?
Tengo un archivo muy grande (~ 400 GB) y necesito eliminar las últimas 2 líneas. Traté de usarlo sed
, pero funcionó durante horas antes de rendirme. ¿Hay alguna forma rápida de hacer esto o estoy atascado sed
?
Respuestas:
No he probado esto en un archivo grande para ver qué tan rápido es, pero debería ser bastante rápido.
Para usar el script para eliminar líneas del final de un archivo:
./shorten.py 2 large_file.txt
Busca hasta el final del archivo, verifica para asegurarse de que el último carácter sea una nueva línea, luego lee cada carácter uno a la vez retrocediendo hasta encontrar tres nuevas líneas y trunca el archivo justo después de ese punto. El cambio se realiza en su lugar.
Editar: he agregado una versión de Python 2.4 en la parte inferior.
Aquí hay una versión para 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)
Aquí hay una versión de 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)
Aquí hay una versión de 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)
puedes probar GNU head
head -n -2 file
head: illegal line count -- -2
Veo que mis sistemas Debian Squeeze / testing (pero no Lenny / stable) incluyen un comando "truncar" como parte del paquete "coreutils".
Con él simplemente podrías hacer algo como
truncate --size=-160 myfile
para eliminar 160 bytes del final del archivo (obviamente, necesita averiguar exactamente cuántos caracteres necesita eliminar).
dd
script simple lo hará (debe especificar el desplazamiento de entrada para obtener el último kilobyte y luego usar tail -2 | LANG= wc -c
, o algo así).
tail
también es eficiente para archivos grandes; puede usarse tail | wc -c
para calcular el número de bytes que se van a recortar.
El problema con sed es que es un editor de flujo: procesará todo el archivo incluso si solo desea realizar modificaciones cerca del final. Así que no importa qué, está creando un nuevo archivo de 400 GB, línea por línea. Cualquier editor que opere en todo el archivo probablemente tendrá este problema.
Si conoce el número de líneas, puede usar head
, pero nuevamente esto crea un nuevo archivo en lugar de alterar el existente en su lugar. Supongo que puede obtener ganancias de velocidad por la simplicidad de la acción.
Es posible que tenga más suerte usando split
para dividir el archivo en partes más pequeñas, editando el último y luego usando cat
para combinarlas nuevamente, pero no estoy seguro de si será mejor. Usaría recuentos de bytes en lugar de líneas, de lo contrario, probablemente no será más rápido en absoluto: todavía va a crear un nuevo archivo de 400 GB.
Pruebe VIM ... No estoy seguro de si funcionará o no, ya que nunca lo he usado en un archivo tan grande, pero lo he usado en archivos más pequeños y más grandes en el pasado, inténtelo.
¿Qué tipo de archivo y en qué formato? Puede ser más fácil usar algo como Perl dependiendo de qué tipo de archivo sea: ¿texto, gráficos, binario? Cómo se formatea: CSV, TSV ...
Si conoce el tamaño del archivo hasta el byte (por ejemplo, 400000000160) y sabe que necesita eliminar exactamente 160 caracteres para quitar las dos últimas líneas, entonces algo como
dd if=originalfile of=truncatedfile ibs=1 count=400000000000
debería hacer el truco. Sin embargo, han pasado años desde que usé dd con ira; Parece que recuerdo que las cosas van más rápido si usas un tamaño de bloque más grande, pero si puedes hacerlo depende de si las líneas que quieres soltar son un buen múltiplo.
dd tiene algunas otras opciones para rellenar registros de texto a un tamaño fijo que podría ser útil como pase preliminar.
Si el comando "truncar" no está disponible en su sistema (vea mi otra respuesta), mire el "truncamiento de man 2" para la llamada del sistema para truncar un archivo a una longitud especificada.
Obviamente, necesita saber cuántos caracteres necesita para truncar el archivo (tamaño menos la longitud del problema dos líneas; no olvide contar los caracteres cr / lf).
¡Y haga una copia de seguridad del archivo antes de intentar esto!
Si prefiere soluciones de estilo unix, puede guardar y truncar líneas interactivas utilizando tres líneas de código (Probado en Mac y Linux).
truncamiento de línea de estilo Unix pequeño + seguro (pide confirmación):
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` )"
Esta solución se basa en algunas herramientas comunes de Unix, pero aún se utiliza perl -e "truncate(file,length)"
como el reemplazo más cercano para truncate(1)
, que no está disponible en todos los sistemas.
También puede usar el siguiente programa integral de shell reutilizable, que proporciona información de uso y cuenta con confirmación de truncamiento, análisis de opciones y manejo de errores.
secuencia de comandos de truncamiento de línea integral :
#!/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
Aquí hay un ejemplo 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" << AQUÍ PS re re w AQUÍ
Los cambios se realizan en su lugar. Esto es más simple y más eficiente que el script python.
ed
tardó 100 veces más en ejecutarse que mi script Python. Solo puedo imaginar cuánto más sería la diferencia para el archivo del OP, que es 7000 veces más grande.
Modificó la respuesta aceptada para resolver un problema similar. Podría modificarse un poco para eliminar n líneas.
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
Y la prueba correspondiente:
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()
Puede usar Vim en modo Ex:
ex -sc '-,d|x' file
-,
seleccione las últimas 2 líneas
d
borrar
x
guardar y cerrar
head -n -2 file