Ho un file molto grande (~ 400 GB) e devo rimuovere le ultime 2 righe da esso. Ho provato a usare sed
, ma ha funzionato per ore prima che mi arrendessi. C'è un modo rapido per farlo, o sono bloccato con sed
?
Ho un file molto grande (~ 400 GB) e devo rimuovere le ultime 2 righe da esso. Ho provato a usare sed
, ma ha funzionato per ore prima che mi arrendessi. C'è un modo rapido per farlo, o sono bloccato con sed
?
Risposte:
Non ho provato questo su un file di grandi dimensioni per vedere quanto è veloce, ma dovrebbe essere abbastanza veloce.
Per utilizzare lo script per rimuovere le righe dalla fine di un file:
./shorten.py 2 large_file.txt
Cerca la fine del file, verifica che l'ultimo carattere sia una nuova riga, quindi legge ogni carattere uno alla volta andando indietro fino a quando non trova tre nuove righe e tronca il file subito dopo quel punto. La modifica è stata effettuata.
Modifica: ho aggiunto una versione di Python 2.4 in basso.
Ecco una versione per 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)
Ecco una versione di 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)
Ecco una versione di 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)
puoi provare GNU head
head -n -2 file
head: illegal line count -- -2
Vedo che i miei sistemi Debian Squeeze / testing (ma non Lenny / stable) includono un comando "truncate" come parte del pacchetto "coreutils".
Con esso potresti semplicemente fare qualcosa del genere
truncate --size=-160 myfile
per rimuovere 160 byte dalla fine del file (ovviamente è necessario capire esattamente quanti caratteri è necessario rimuovere).
dd
script lo farà (devi specificare l'offset di input per ottenere l'ultimo kilobyte e quindi usare tail -2 | LANG= wc -c
, o sth).
tail
è efficace anche per file di grandi dimensioni - può essere utilizzato tail | wc -c
per calcolare il numero di byte da tagliare.
Il problema con sed è che si tratta di un editor di stream: elaborerà l'intero file anche se si desidera apportare modifiche solo alla fine. Quindi, indipendentemente da cosa, stai creando un nuovo file da 400 GB, riga per riga. Qualsiasi editor che opera sull'intero file avrà probabilmente questo problema.
Se conosci il numero di righe, puoi usarlo head
, ma di nuovo questo crea un nuovo file invece di alterare quello esistente in atto. Potresti ottenere guadagni di velocità dalla semplicità dell'azione, immagino.
Si potrebbe avere più fortuna usando split
per rompere il file in pezzi più piccoli, modificando l'ultimo, e quindi utilizzando cat
per combinare di nuovo, ma non sono sicuro se sarà meglio. Vorrei usare i conteggi dei byte piuttosto che le linee, altrimenti probabilmente non sarà affatto più veloce - stai ancora creando un nuovo file da 400 GB.
Prova VIM ... Non sono sicuro che farà il trucco o meno, dato che non l'ho mai usato su un file così grande, ma in passato lo ho usato su file più piccoli e più grandi.
Che tipo di file e in quale formato? Potrebbe essere più facile usare qualcosa come Perl a seconda del tipo di file: testo, grafica, binario? Come viene formattato - CSV, TSV ...
Se conosci la dimensione del file al byte (ad esempio 400000000160) e sai che devi rimuovere esattamente 160 caratteri per eliminare le ultime due righe, quindi qualcosa di simile
dd if=originalfile of=truncatedfile ibs=1 count=400000000000
dovrebbe fare il trucco. Sono passati secoli da quando ho usato dd in rabbia; Mi sembra di ricordare che le cose vanno più veloci se usi blocchi di dimensioni maggiori, ma se puoi farlo dipende dal fatto che le linee che vuoi eliminare siano in un bel multiplo.
dd ha alcune altre opzioni per riempire i record di testo a una dimensione fissa che potrebbe essere utile come passaggio preliminare.
Se il comando "tronca" non è disponibile sul tuo sistema (vedi la mia altra risposta), guarda "man 2 truncate" per la chiamata di sistema per troncare un file ad una lunghezza specificata.
Ovviamente devi sapere a quanti caratteri devi troncare il file (dimensione meno la lunghezza del problema due righe; non dimenticare di contare i caratteri cr / lf).
E fai un backup del file prima di provare questo!
Se preferisci soluzioni in stile unix, puoi avere il salvataggio e il troncamento interattivo delle linee usando tre righe di codice (testato su Mac e Linux).
troncamento della linea piccolo + sicuro in stile unix (richiede conferma):
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` )"
Questa soluzione si basa su alcuni strumenti unix comuni, ma utilizza ancora perl -e "truncate(file,length)"
come sostituto più vicino per truncate(1)
, che non è disponibile su tutti i sistemi.
È inoltre possibile utilizzare il seguente programma shell riutilizzabile completo, che fornisce informazioni sull'utilizzo e funzionalità di conferma del troncamento, analisi delle opzioni e gestione degli errori.
script completo di troncamento di riga :
#!/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
Ecco un esempio di utilizzo:
$ 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" << QUI $ d d w QUI
vengono apportate modifiche. Questo è più semplice ed efficiente dello script Python.
ed
100 volte più lunga del mio script Python. Posso solo immaginare quanto maggiore sarebbe la differenza per il file del PO che è 7000 volte più grande.
Modificata la risposta accettata per risolvere un problema simile. Potrebbe essere modificato un po 'per rimuovere n linee.
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 il test corrispondente:
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()
Puoi usare Vim in modalità Ex:
ex -sc '-,d|x' file
-,
seleziona le ultime 2 righe
d
Elimina
x
salva e chiudi
head -n -2 file