Alternative Sed pour rechercher et remplacer sur de très longues lignes

9

J'ai des fichiers qui ont été générés par un programme qui n'a pas mis de nouvelles lignes à la fin des enregistrements. Je veux mettre des retours à la ligne entre les enregistrements, et je peux le faire avec un simple script sed:

sed -e 's/}{/}\n{/g'

Le problème est que les fichiers d'entrée ont une taille de plusieurs gigaoctets, et donc les lignes d'entrée à sed ont une longueur de plusieurs Go. sed essaie de garder une ligne en mémoire, ce qui ne fonctionne pas dans ce cas. J'ai essayé l' --unbufferedoption, mais cela semblait simplement la ralentir et ne lui permettait pas de se terminer correctement.

Tom Panning
la source
Serait-il possible de télécharger un exemple de fichier d'entrée quelque part pour que nous puissions essayer quelques idées?
mkc
3
Peut-être pourriez-vous d'abord utiliser trpour traduire }en \npuis utiliser sedpour ajouter un }à la fin de chaque ligne? Comme ceci:tr '}' '\n' < your_file.txt| sed 's/$/}/'
user43791
L'ajout d'une nouvelle ligne à la fin du fichier est-il utile? Comme:printf "\n" >> file
nounou
1
@Ketan, je suppose que l'écriture d'un fichier avec 78 caractères poubelles suivis par }{répétition jusqu'à ce qu'il soit de plusieurs gigaoctets suffirait.
nounou
@nanny - bon point - mais où obtenez-vous 78? Si les enregistrements sont déjà bloqués, ce dd if=file cbs=80 conv=unblockserait le cas - mais c'est rarement aussi simple.
mikeserv

Réponses:

7

Vous pouvez utiliser un autre outil qui vous permet de définir le séparateur d'enregistrement d'entrée. Par exemple

  • Perl

    perl -pe 'BEGIN{ $/="}{" } s/}{/}\n{/g' file
    

    La variable spéciale $/est le séparateur d'enregistrement d'entrée. Le }{définir pour définir les lignes se terminant par }{. De cette façon, vous pouvez réaliser ce que vous voulez sans lire le tout dans la mémoire.

  • mawk ou gawk

    awk -v RS="}{" -vORS= 'NR > 1 {print "}\n{"}; {print}' file 
    

    C'est la même idée. RS="}{"définit le séparateur d'enregistrement sur }{et ensuite vous imprimez }, une nouvelle ligne, {(sauf pour le premier enregistrement) et l'enregistrement en cours.

terdon
la source
3

Perl à la rescousse:

perl -i~ -e ' $/ = \1024;
              while (<>) {
                  print "\n" if $closing and /^{/;
                  undef $closing;
                  s/}{/}\n{/g;
                  print;
                  $closing = 1 if /}$/;
              } ' input1 input2

Le réglage $/sur \1024lira le fichier par blocs de 1024 octets. La $closingvariable gère le cas où un morceau se termine }et le suivant commence par {.

choroba
la source
1
+1, probablement la meilleure solution; les autres solutions perl / awk fonctionnent bien aussi, mais que se passe-t-il si le premier séparateur d'enregistrement se produit après environ 17 Go de caractères?
don_crissti
2

Tu devrais faire:

{ <infile tr \} \\n;echo {; } | paste -d'}\n' - /dev/null >outfile

C'est probablement la solution la plus efficace.

Cela met un {}pour protéger toutes les données de fin possibles. Avec un trprocessus de plus , vous pouvez échanger cela et faire une ligne vierge en tête du premier {champ. Comme...

tr {} '}\n'| paste -d{\\0 /dev/null - | tr {}\\n \\n{}

Ainsi, le premier, avec les données d'exemple de don, fait:

printf '{one}{two}{three}{four}' |
{ tr \} \\n; echo {; }           |
paste -d'}\n' - /dev/null
{one}
{two}
{three}
{four}
{}

... et le second fait ...

printf '{one}{two}{three}{four}'      |
tr {} '}\n'| paste -d{\\0 /dev/null - |
tr {}\\n \\n{}
#leading blank
{one}
{two}
{three}
{four}

Il n'y a pas de nouvelle ligne de fin pour le deuxième exemple - bien qu'il y en ait une pour le premier.

mikeserv
la source
0

Un sedutilitaire de type binaire appelébbe

Je trouve qu'il est plus facile de conserver une syntaxe de type sed dans ce cas.

Je beaucoup préfère utiliser l' bbeutilitaire (disponible via votre {uni, Linu} l'installation du package de x, éq apt-get). Ou ici, si vous faites partie de la foule git, bien que je n'ai pas personnellement vérifié ce lien particulier.

1. Il prend en charge l' s/before/after/idiome

Il s'agit d'un "éditeur de blocs binaires", qui prend en charge les opérations de type sed (entre autres). Cela inclut l' s/before/after/idiome de substitution super commun dont vous avez besoin. Notez, car il n'y a pas de lignes en soi du bbepoint de vue de, il n'y a pas de "g global" à la fin de la commande.

Comme test rapide (notez le requis -e):

$ echo hello | bbe -e 's/l/(replaced)/'

produit:

he(replaced)(replaced)o

2. Dans votre cas spécifique de }{la }\n{conversion

Donc, si nous avions un fichier volumineux rempli d'un million de numéros dans (disons) le format {1}{2}{3}... {1000000}sans retour chariot, nous pourrions échanger facilement }{avec }\n{, et avoir tous les numéros un par ligne.

Ce serait avec cette bbecommande:

bbe -e 's/}{/}\n{/'

Comme testé dans cette boucle zsh, dont nous saisissons juste la queue de:

$ for ((num=0; num<1000000; num++)) do; echo -n "{$num}"; done | bbe -e 's/}{/}\n{/' | tail

Ce qui produirait ceci:

{999990}
{999991}
{999992}
{999993}
{999994}
{999995}
{999996}
{999997}
{999998}
{999999}

(sans retour de chariot arrière bien sûr.)

tgm1024 - Monica a été maltraitée
la source