J'ai un très gros fichier (~ 400 Go), et je dois en supprimer les 2 dernières lignes. J'ai essayé d'utiliser sed
, mais il a fonctionné pendant des heures avant d'abandonner. Y a-t-il un moyen rapide de le faire, ou suis-je coincé avec sed
?
J'ai un très gros fichier (~ 400 Go), et je dois en supprimer les 2 dernières lignes. J'ai essayé d'utiliser sed
, mais il a fonctionné pendant des heures avant d'abandonner. Y a-t-il un moyen rapide de le faire, ou suis-je coincé avec sed
?
Réponses:
Je n'ai pas essayé cela sur un gros fichier pour voir à quelle vitesse il est, mais cela devrait être assez rapide.
Pour utiliser le script pour supprimer des lignes à la fin d'un fichier:
./shorten.py 2 large_file.txt
Il cherche à la fin du fichier, vérifie que le dernier caractère est une nouvelle ligne, puis lit chaque caractère un par un en remontant jusqu'à ce qu'il trouve trois nouvelles lignes et tronque le fichier juste après ce point. Le changement est effectué sur place.
Edit: j'ai ajouté une version Python 2.4 en bas.
Voici une version pour 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)
Voici une version 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)
Voici une version 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)
vous pouvez essayer la tête GNU
head -n -2 file
head: illegal line count -- -2
Je vois que mes systèmes Debian Squeeze / testing (mais pas Lenny / stable) incluent une commande "tronquer" dans le cadre du paquet "coreutils".
Avec cela, vous pouvez simplement faire quelque chose comme
truncate --size=-160 myfile
pour supprimer 160 octets à la fin du fichier (vous devez évidemment déterminer exactement combien de caractères vous devez supprimer).
dd
script fera cela (vous devez spécifier le décalage d'entrée pour obtenir le dernier kilo-octet, puis utiliser tail -2 | LANG= wc -c
, ou quelque chose comme ça).
tail
est également efficace pour les fichiers volumineux - peut être utilisé tail | wc -c
pour calculer le nombre d'octets à supprimer.
Le problème avec sed est qu'il s'agit d'un éditeur de flux - il traitera l'intégralité du fichier même si vous ne souhaitez apporter des modifications qu'à la fin. Quoi qu'il en soit, vous créez un nouveau fichier de 400 Go, ligne par ligne. Tout éditeur qui opère sur l'ensemble du fichier aura probablement ce problème.
Si vous connaissez le nombre de lignes, vous pouvez utiliser head
, mais encore une fois, cela crée un nouveau fichier au lieu de modifier celui existant en place. Vous pourriez obtenir des gains de vitesse grâce à la simplicité de l'action, je suppose.
Vous pourriez avoir plus de chance en utilisant split
pour diviser le fichier en petits morceaux, en éditant le dernier, puis en utilisant cat
pour les combiner à nouveau, mais je ne suis pas sûr que ce sera mieux. J'utiliserais le nombre d'octets plutôt que les lignes, sinon ce ne sera probablement pas plus rapide du tout - vous allez toujours créer un nouveau fichier de 400 Go.
Essayez VIM ... Je ne sais pas s'il fera l'affaire ou non, car je ne l'ai jamais utilisé sur un si gros fichier, mais je l'ai utilisé sur des fichiers plus petits et plus grands dans le passé, essayez-le.
Quel type de fichier et dans quel format? Peut être plus facile à utiliser quelque chose comme Perl selon le type de fichier - texte, graphiques, binaire? Comment est-il formaté - CSV, TSV ...
Si vous connaissez la taille du fichier en octets (400000000160 disons) et que vous savez que vous devez supprimer exactement 160 caractères pour supprimer les deux dernières lignes, alors quelque chose comme
dd if=originalfile of=truncatedfile ibs=1 count=400000000000
devrait faire l'affaire. Cela fait longtemps que je n'ai pas utilisé dd dans la colère; Je semble me souvenir que les choses vont plus vite si vous utilisez une taille de bloc plus grande, mais si vous pouvez le faire, cela dépend si les lignes que vous souhaitez supprimer sont à un bon multiple.
dd a quelques autres options pour compléter les enregistrements de texte à une taille fixe qui pourrait être utile comme passage préliminaire.
Si la commande "tronquer" n'est pas disponible sur votre système (voir mon autre réponse), regardez la "man 2 tronquer" pour l'appel système pour tronquer un fichier à une longueur spécifiée.
Évidemment, vous devez savoir combien de caractères vous devez tronquer le fichier (taille moins la longueur du problème deux lignes; n'oubliez pas de compter les caractères cr / lf).
Et faites une sauvegarde du fichier avant d'essayer!
Si vous préférez les solutions de style Unix, vous pouvez avoir une troncature de ligne d'enregistrement et interactive en utilisant trois lignes de code (testé sur Mac et Linux).
petite + troncature de ligne de style Unix sûre (demande de confirmation):
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` )"
Cette solution repose sur quelques outils Unix courants, mais utilise toujours perl -e "truncate(file,length)"
le remplacement le plus proche truncate(1)
, qui n'est pas disponible sur tous les systèmes.
Vous pouvez également utiliser le programme shell réutilisable complet suivant, qui fournit des informations d'utilisation et propose une confirmation de troncature, une analyse des options et une gestion des erreurs.
script de troncature de ligne complet :
#!/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
Voici un exemple d'utilisation:
$ 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" << ICI $ ré ré w ICI
des modifications sont apportées sur place. C'est plus simple et plus efficace que le script python.
ed
pris 100 fois plus de temps à exécuter que mon script Python. Je ne peux qu'imaginer à quel point la différence serait encore plus grande pour le fichier OP qui est 7000 fois plus grand.
Modification de la réponse acceptée pour résoudre un problème similaire. Pourrait être modifié un peu pour supprimer n lignes.
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
Et le test correspondant:
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()
Vous pouvez utiliser Vim en mode Ex:
ex -sc '-,d|x' file
-,
sélectionner les 2 dernières lignes
d
supprimer
x
sauver et fermer
head -n -2 file