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.


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.


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 {.


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.


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.)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.