J'ai un énorme (70 Go), une ligne , un fichier texte et je veux remplacer une chaîne (jeton) en elle. Je souhaite remplacer le jeton <unk>
par un autre jeton factice ( problème de gant ).
J'ai essayé sed
:
sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
mais le fichier de sortie corpus.txt.new
a zéro octet!
J'ai aussi essayé d'utiliser Perl:
perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
mais j'ai eu une erreur de mémoire insuffisante.
Pour les fichiers plus petits, les deux commandes ci-dessus fonctionnent.
Comment puis-je remplacer une chaîne est un tel fichier? C'est une question liée, mais aucune des réponses n'a fonctionné pour moi.
Edit : Qu'en est-il de scinder le fichier en morceaux de 10 Go (ou quoi que ce soit) chacun, d'appliquer sed
chacun d'eux et de les fusionner ensuite cat
? Cela a-t-il du sens? Y a-t-il une solution plus élégante?
la source
split
avec la-b
définition de la taille des fichiers de morceaux en octets. Traitez chacun à son tour en utilisantsed
et le ré-assembler. Il y a un risque, c'est que l'<unk>
on puisse diviser en deux fichiers sans les retrouver ...Réponses:
Les outils de traitement de texte habituels ne sont pas conçus pour gérer les lignes qui ne rentrent pas dans la RAM. Ils ont tendance à travailler en lisant un enregistrement (une ligne), en le manipulant et en affichant le résultat, puis en passant à l'enregistrement suivant (ligne).
Si un caractère ASCII apparaît fréquemment dans le fichier et n'apparaît pas dans
<unk>
ou<raw_unk>
, vous pouvez l'utiliser comme séparateur d'enregistrement. Comme la plupart des outils n'autorisent pas les séparateurs d'enregistrement personnalisés, permutez entre ce caractère et les nouvelles lignes.tr
traite des octets, pas des lignes, de sorte qu'il ne se soucie pas de la taille d'un enregistrement. En supposant que cela;
fonctionne:Vous pouvez également ancrer le premier caractère du texte que vous recherchez, en supposant qu'il ne soit pas répété dans le texte recherché et qu'il apparaisse assez souvent. Si le fichier peut commencer par
unk>
, modifiez la commande sedsed '2,$ s/…
pour éviter une correspondance parasite.Sinon, utilisez le dernier caractère.
Notez que cette technique suppose que sed fonctionne de manière transparente sur un fichier qui ne se termine pas par une nouvelle ligne, c'est-à-dire qu'il traite la dernière ligne partielle sans la tronquer ni l'ajout d'une nouvelle ligne. Cela fonctionne avec GNU sed. Si vous pouvez choisir le dernier caractère du fichier comme séparateur d'enregistrement, vous éviterez tout problème de portabilité.
la source
awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}'
Non?-0
et la valeur octale d'un caractère, ou à l'intérieur du script, il peut être défini avec une variable spéciale$/
awk
évitez de passer deux fois au fluxtr
. Alors serait-il encore plus lent?tr
est très rapide et le tuyau peut même être parallélisé.Pour un si gros fichier, une possibilité est Flex. Let
unk.l
Être:Ensuite, compilez et exécutez:
la source
make
a des règles par défaut pour cela, à la place du flex / cc, vous pouvez ajouter un%option main
comme première ligne de unk.l puis justemake unk
. J'utilise plus ou moins par réflexe%option main 8bit fast
et j'aiexport CFLAGS='-march=native -pipe -Os'
dans mes.bashrc
.%option main
+make
+ optionnellementCFLAGS
c'est un très beau tour !! Est-ce que-march=native
le comportement par défaut?Par conséquent, vous ne disposez pas de suffisamment de mémoire physique (RAM) pour contenir le fichier entier en une fois, mais sur un système 64 bits, vous disposez de suffisamment d' espace d'adressage virtuel pour mapper l'ensemble du fichier. Les mappages virtuels peuvent être utiles comme un simple piratage dans des cas comme celui-ci.
Les opérations nécessaires sont toutes incluses dans Python. Il y a plusieurs subtilités gênantes, mais cela évite d'avoir à écrire du code C. En particulier, il faut éviter de copier le fichier en mémoire, ce qui éliminerait complètement le problème. Sur le plan positif, vous obtenez un rapport d’erreurs gratuitement (python "exceptions") :).
la source
search
peut contenir un caractère NUL. Et je remarque que l’autre version C ici ne prend pas en charge les caractères NUL dansreplace
.). Vous pouvez très bien dériver la version C à des fins de comparaison. Cependant, rappelez-vous que ma version inclut un rapport d'erreur de base pour les opérations effectuées. La version C serait au moins plus gênante pour lire IMO, lorsque le rapport d’erreur est inclus.Il existe un
replace
utilitaire dans le paquet mariadb-server / mysql-server. Il remplace des chaînes simples (expressions régulières) pas et contrairement à grep / sed / awkreplace
ne se soucie pas\n
et\0
. La consommation de mémoire est constante quel que soit le fichier d'entrée (environ 400 Ko sur ma machine).Bien sûr, vous n'avez pas besoin de faire tourner un serveur mysql pour l'utiliser
replace
, il est seulement emballé de cette façon dans Fedora. Il peut être emballé séparément dans d’autres systèmes d’exploitation / systèmes d’exploitation.la source
Je pense que la version C pourrait fonctionner beaucoup mieux:
EDIT: Modifié selon les suggestions des commentaires. Également corrigé un bug avec le motif
<<unk>
.la source
memcpy
La vitesse (c’est-à-dire le goulot d’étranglement de la mémoire) est d’environ 12 Go / seconde sur un processeur x86 récent (par exemple, Skylake). Même avec la surcharge d'appels système stdio +, pour un fichier de 30 Mo à chaud dans le cache disque, je m'attendrais peut-être à 1 Go / seconde pour une mise en œuvre efficace. Avez-vous compilé avec l'optimisation désactivée ou les E / S à un caractère à la fois sont-elles vraiment aussi lentes?getchar_unlocked
/putchar_unlocked
pourrait aider, mais il est certainement préférable de lire / écrire en morceaux de peut-être 128kiB (la moitié de la taille du cache L2 sur la plupart des processeurs x86, donc vous frappez surtout en L2 en boucle après lecture)fix
programme pour"<<unk>"
toujours ne fonctionne pas si ilpattern
commence par une séquence répétée de caractères (cela ne fonctionnerait pas si vous tentiez de remplacer aardvark par zebra et si vous aviez entré aaardvak, ou si vous essayiez de remplacer ababc et eu entrée de abababc). En général, vous ne pouvez pas avancer du nombre de caractères que vous avez lus, à moins que vous sachiez qu’il n’ya aucune possibilité qu’une correspondance commence avec les caractères que vous avez lus.GNU
grep
peut vous montrer le décalage des correspondances dans des fichiers "binaires", sans avoir à lire des lignes entières en mémoire. Vous pouvez ensuite utiliserdd
pour lire jusqu’à ce décalage, ignorer la correspondance, puis continuer à copier à partir du fichier.Pour
dd
des raisons de rapidité, j'ai scindé le en une grande lecture de la taille de bloc 1048576 et une lecture plus petite d'un octet à la fois, mais cette opération sera encore un peu lente sur un fichier aussi volumineux. Lagrep
sortie est, par exemple,13977:<unk>
et elle est divisée sur les deux points par la lecture en variablesoffset
etpattern
. Nous devons garder tracepos
du nombre d'octets déjà copiés du fichier.la source
Voici une autre ligne de commande UNIX unique qui peut fonctionner mieux que d’autres options, car vous pouvez "rechercher" une "taille de bloc" performante. Pour que cela soit robuste, vous devez savoir que vous avez au moins un espace dans chaque X caractères, où X est votre "taille de bloc" arbitraire. Dans l'exemple ci-dessous, j'ai choisi une "taille de bloc" de 1024 caractères.
Ici, fold se chargera jusqu'à 1024 octets, mais le -s s'assurera qu'il saute sur un espace s'il y en a au moins un depuis la dernière pause.
La commande sed est à vous et fait ce que vous attendez.
Ensuite, la commande tr "déplie" le fichier en convertissant les nouvelles lignes insérées.
Vous devriez envisager d’essayer des blocs plus grands pour voir s’il fonctionne plus rapidement. Au lieu de 1024, vous pouvez essayer 10240, 102400 et 1048576 pour l'option -w de fold.
Voici un exemple décomposé à chaque étape qui convertit tous les N en minuscules:
Vous devrez ajouter une nouvelle ligne à la toute fin du fichier, le cas échéant, car la commande tr le supprimera.
la source
En utilisant
perl
Gérer vos propres tampons
Vous pouvez utiliser
IO::Handle
« ssetvbuf
pour gérer les tampons par défaut, ou vous pouvez gérer vos propres tampons avecsysread
etsyswrite
. Vérifiezperldoc -f sysread
etperldoc -f syswrite
pour plus d’informations, essentiellement, ils sautent la mémoire tampon io.Ici, nous lançons notre propre tampon IO, mais nous le faisons manuellement et arbitrairement sur 1024 octets. Nous ouvrons également le fichier pour RW, donc nous le faisons tous en même temps sur le même FH.
Si vous allez suivre cette voie
<unk>
que<raw_unk>
la taille en octets est identique.CHUNKSIZE
limite si vous remplacez plus d'un octet.la source
<unk>
tombe sur une limite entre des morceaux?Vous pouvez essayer bbe ( éditeur de blocs binaires ), un "
sed
pour les fichiers binaires".J'ai eu du succès à l'utiliser sur un fichier texte de 7 Go sans
EOL
caractères, remplaçant plusieurs occurrences d'une chaîne par une autre de longueur différente. Sans aucune tentative d'optimisation, le débit de traitement moyen était supérieur à 50 Mo / s.la source
Avec
perl
, vous pouvez travailler avec des enregistrements de longueur fixe comme:Et espérons qu’il n’y aura pas de
<unk>
débordement entre deux de ces enregistrements de 100 Mo.la source
while read -N 1000 chunk;
(1000
choisi comme exemple). La solution pour le<unk>
, cassé entre les morceaux, consiste en deux passages dans le fichier: le premier avec les morceaux de 100 Mo et le second avec les morceaux '100 Mo + 5 octets'. Mais ce n'est pas la solution optimale dans le cas du fichier de 70 Go.<unk>
.<unk>
occurrences soient très éloignées, sinon, utilisez$/ = ">"
ets/<unk>\z/<raw_unk>/g
) exacte.Voici un petit programme Go qui effectue la tâche (
unk.go
):Il suffit de le construire avec
go build unk.go
et l’exécuter en tant que./unk <input >output
.MODIFIER:
Désolé, je n'ai pas lu que tout est dans une ligne, alors j'ai essayé de lire le fichier caractère par caractère maintenant.
EDIT II:
Appliqué même correctif que pour le programme C.
la source
scanner.Split(bufio.ScanRunes)
fait la magie.go doc bufio.MaxScanTokenSize
la taille de la mémoire tampon par défaut.C
programme, cela ne fonctionne pas pour remplacer aardvark par zebra par une entrée de aaardvark.Cela peut être excessif pour un fichier de 70 Go et une simple recherche et remplacement, mais le framework Hadoop MapReduce résoudrait votre problème sans aucun frais (choisissez l’option 'Noeud unique' lors de la configuration pour l’exécuter localement) - et peut être mise à l'échelle à une capacité infinie à l'avenir sans avoir à modifier votre code.
Le tutoriel officiel à l' adresse https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html utilise Java (extrêmement simple) mais vous pouvez trouver des bibliothèques clientes pour Perl ou quelle que soit la langue que vous avez envie d'utiliser.
Par conséquent, si, par la suite, vous réalisez des opérations plus complexes sur des fichiers texte de 7 000 Go, et que vous devez le faire 100 fois par jour, vous pouvez répartir la charge de travail sur plusieurs nœuds que vous avez provisionnés ou qui sont automatiquement provisionnés par un cloud. cluster Hadoop basé.
la source
Toutes les suggestions précédentes nécessitent de lire le fichier entier et de l'écrire au complet. Cela prend non seulement beaucoup de temps, mais nécessite également 70 Go d'espace libre.
1) serait-il correctement Si je vous comprends cas spécifique acceptable pour remplacer <UNK> avec une autre chaîne de la même longueur?
2a) Y a-t-il plusieurs occurrences? 2b) Si oui, savez-vous combien?
Je suis sûr que vous avez déjà résolu ce problème de l'année en cours et j'aimerais savoir quelle solution vous avez utilisée.
Je proposerais une solution (probablement en C) qui lirait les BLOCS du fichier en cherchant chacun la chaîne en tenant compte du croisement possible des blocs. Une fois la substitution trouvée, remplacez la chaîne par la longueur SAME en alternance et écrivez uniquement ce BLOCK. Continuant pour le nombre connu d'occurrences ou jusqu'à la fin du fichier. Cela nécessiterait aussi peu que le nombre d'occurrences écrites et au plus le double (si chaque occurrence était divisée en 2 blocs). Cela ne nécessiterait AUCUN espace supplémentaire!
la source
Si nous avons un montant minimum de
<unk>
(comme prévu par la loi de Zipf),la source
sed
lit une ligne à la fois dans la mémoire, peu importe. Il ne pourra pas s'adapter à cette ligne.sed
n'effectuera pas la mise en mémoire tampon des entrées / sorties lors de l'utilisation de cet indicateur. Je ne vois pas qu'il lira des lignes partielles.