Remplacer la chaîne contenant la nouvelle ligne dans un fichier énorme

16

Quelqu'un connaît-il un outil non basé sur la ligne pour rechercher / remplacer des chaînes "binaires" d'une manière quelque peu efficace en mémoire? Voir aussi cette question .

J'ai un fichier texte de + 2 Go que j'aimerais traiter de manière similaire à ce que cela semble faire:

sed -e 's/>\n/>/g'

Cela signifie que je souhaite supprimer toutes les nouvelles lignes qui se produisent après un > , mais pas ailleurs, de sorte que cela soit exclu tr -d.

Cette commande (que j'ai obtenue de la réponse à une question similaire ) échoue avec couldn't re-allocate memory:

sed --unbuffered ':a;N;$!ba;s/>\n/>/g'

Alors, existe-t-il d'autres méthodes sans recourir à C? Je déteste Perl, mais je suis prêt à faire une exception dans ce cas :-)

Je ne sais pas avec certitude de caractère qui ne se produit pas dans les données, donc remplacement temporaire \n par un autre caractère est quelque chose que j'aimerais éviter si possible.

Des bonnes idées, quelqu'un?

MattBianco
la source
Avez-vous essayé l'option --unbuffered?
ctrl-alt-delor
Avec ou sans --unbufferedmanque de mémoire
MattBianco
Que fait $!-il?
ctrl-alt-delor
Quel est le problème avec la première commande sed. Le second semble tout lire dans l'espace des motifs, je ne sais pas $!si c'est le cas. Je pense que cela aura besoin de BEAUCOUP de mémoire.
ctrl-alt-delor
Le problème est que sed lit tout sous forme de lignes, c'est pourquoi la première commande ne supprime pas les retours à la ligne, car elle renvoie à nouveau le texte ligne par ligne. La deuxième commande est juste une solution de contournement. Je pense que ce sedn'est pas l'outil approprié dans ce cas.
MattBianco

Réponses:

14

C'est vraiment trivial en Perl, vous ne devriez pas le détester!

perl -i.bak -pe 's/>\n/>/' file

Explication

  • -i: modifiez le fichier en place et créez une sauvegarde de l'original appelé file.bak. Si vous ne voulez pas de sauvegarde, utilisez simplement à la perl -i -peplace.
  • -pe: lire le fichier d'entrée ligne par ligne et imprimer chaque ligne après avoir appliqué le script donné en -e.
  • s/>\n/>/: la substitution, tout comme sed.

Et voici une awkapproche:

awk  '{if(/>$/){printf "%s",$0}else{print}}' file2 
terdon
la source
3
+1. awk golf:awk '{ORS=/>$/?"":"\n"}1'
glenn jackman
1
Pourquoi je n'aime pas perl en général, c'est la même raison pour laquelle j'ai choisi cette réponse (ou en fait votre commentaire à la réponse de Gnouc): la lisibilité. L'utilisation de perl -pe avec un simple "motif sed" est beaucoup plus lisible qu'une expression sed complexe.
MattBianco
3
@MattBianco assez juste mais, juste pour que vous le sachiez, cela n'a rien à voir avec Perl. Le lookbehind que Gnouc a utilisé est une caractéristique de certains langages d'expression régulière (y compris, mais sans s'y limiter, les PCRE), pas du tout la faute de Perl. De plus, après avoir présenté cette monstruosité sed ':a;N;$!ba;s/>\n/>/g'dans votre question, vous avez renoncé à votre droit de vous plaindre de la lisibilité! : P
terdon
@glennjackman nice! Je jouais avec la foo ? bar : bazconstruction mais je ne pouvais pas la faire fonctionner.
terdon
@terdon: Ouais, mon erreur. Supprime-le.
cuonglm
7

Une perlsolution:

$ perl -pe 's/(?<=>)\n//'

Explication

  • s/// est utilisé pour la substitution de chaînes.
  • (?<=>) est le modèle de lookbehind.
  • \n correspond à la nouvelle ligne.

Le motif entier signifie supprimer tous les sauts de ligne qui l'ont >précédé.

cuonglm
la source
2
voulez-vous commenter les différentes parties du programme? Je cherche toujours à apprendre.
MattBianco
2
Pourquoi s'embêter avec le lookbehind? Pourquoi pas juste s/>\n/>/?
terdon
1
ou s/>\K\n//fonctionnerait également
glenn jackman
@terdon: Juste la première chose que j'ai pensé, retirer au lieu de remplacer
cuonglm
@glennjackman: bon point!
cuonglm
3

Que dis-tu de ça:

sed ':loop
  />$/ { N
    s/\n//
    b loop
  }' file

Pour GNU sed, vous pouvez également essayer d'ajouter l' option -u( --unbuffered) selon la question. GNU sed en est également satisfait en tant que simple doublure:

sed ':loop />$/ { N; s/\n//; b loop }' file
Graeme
la source
Cela ne supprime pas le dernier \nsi le fichier se termine >\n, mais c'est probablement préférable de toute façon.
Stéphane Chazelas
@ StéphaneChazelas, pourquoi la clôture }doit-elle être dans une expression distincte? cela ne fonctionnera-t-il pas comme une expression multiligne?
Graeme
1
Cela fonctionnera dans les seds POSIX avec b loop\n}ou -e 'b loop' -e '}'mais pas aussi b loop;}et certainement pas b loop}parce que }et ;sont valides dans les noms d'étiquette (bien que personne de bon sens ne l'utiliserait. Et cela signifie que GNU sed n'est pas conforme à POSIX) et la }commande doit être séparée de la bcommande.
Stéphane Chazelas
@ StéphaneChazelas, GNU sedest satisfait de tout ce qui précède même avec --posix! La norme a également les éléments suivants pour les expressions d'accolade - The list of sed functions shall be surrounded by braces and separated by <newline>s. Cela ne signifie-t-il pas que les points-virgules ne doivent être utilisés qu'en dehors des accolades?
Graeme
@mikeserv, la boucle est nécessaire pour gérer les lignes consécutives se terminant par >. L'original n'en a jamais eu, comme l'a souligné Stéphane.
Graeme
1

Vous devriez pouvoir l'utiliser sedavec la Ncommande, mais l'astuce sera de supprimer une ligne de l'espace de motif chaque fois que vous en ajouterez une autre (de sorte que l'espace de motif ne contienne toujours que 2 lignes consécutives, au lieu d'essayer de lire dans son intégralité fichier) - essayez

sed ':a;$!N;s/>\n/>/;P;D;ba'

EDIT: après avoir relu le fameux Sed One-Liners de Peteris Krumins expliqué, je crois qu'une meilleure sedsolution serait

sed -e :a -e '/>$/N; s/\n//; ta'

qui ajoute seulement la ligne suivante dans le cas où il a déjà fait une >correspondance à la fin, et devrait boucler conditionnellement pour gérer le cas des lignes correspondantes consécutives (c'est le 39 de Krumin . Ajouter une ligne à la suivante si elle se termine par une barre oblique inverse) "\" à l' exception de la substitution de >for \comme caractère de jointure et du fait que le caractère de jointure est conservé dans la sortie).

tournevis
la source
2
Cela ne fonctionne pas si 2 lignes consécutives se terminent >(c'est aussi spécifique à GNU)
Stéphane Chazelas
1

sedne fournit pas un moyen d'émettre la sortie sans une nouvelle ligne finale. Votre approche utilisant Nfondamentalement fonctionne, mais stocke des lignes incomplètes en mémoire, et peut donc échouer si les lignes deviennent trop longues (les implants sed ne sont généralement pas conçus pour gérer des lignes extrêmement longues).

Vous pouvez utiliser awk à la place.

awk '{if (/<$/) printf "%s", $0; else print}'

Une autre approche consiste à trremplacer le caractère de retour à la ligne par un caractère «ennuyeux» qui se produit fréquemment. L'espace peut fonctionner ici - choisissez un caractère qui a tendance à apparaître sur chaque ligne ou au moins une grande proportion de lignes dans vos données.

tr ' \n' '\n ' | sed 's/> />/g' | tr '\n ' ' \n'
Gilles 'SO- arrête d'être méchant'
la source
Les deux méthodes sont déjà démontrées ici pour un meilleur effet dans d'autres réponses. Et son approche avec sedne fonctionne pas sans un tampon de 2,5 Go.
mikeserv
Quelqu'un a-t-il mentionné awk? Oh, je l'ai manqué, je n'avais remarqué perl dans la réponse de terdon que pour une raison quelconque. Personne n'a mentionné l' trapproche - mikeserv, vous avez publié une approche différente (valide, mais moins générique) qui se trouve également être utilisée tr.
Gilles 'SO- arrête d'être méchant'
des sons valides, mais moins génériques , comme vous venez de l'appeler une solution fonctionnelle et ciblée. Je pense qu'il est difficile de faire valoir qu'une telle chose n'est pas utile, ce qui est étrange car elle a 0 votes positifs. La plus grande différence que je peux voir entre ma propre solution et votre offre plus générique , c'est que la mienne résout spécifiquement un problème, alors que la vôtre en général. Cela pourrait en valoir la peine - et je peux même inverser mon vote - mais il y a aussi la question embêtante des 7 heures entre eux et le thème récurrent de vos réponses imitant les autres. Pouvez-vous expliquer cela?
mikeserv
1

qu'en est-il d'utiliser ed?

ed -s test.txt <<< $'/fruits/s/apple/banana/g\nw'

(via http://wiki.bash-hackers.org/howto/edit-ed )

andrej
la source
édité, il n'y a plus de dépendance sur le site web
andrej
-1

Il existe de nombreuses façons de le faire, et la plupart ici sont vraiment bonnes, mais je pense que celui-ci est mon préféré:

tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'

Ou même:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n'
mikeserv
la source
Je ne peux pas obtenir du tout votre première réponse. Bien que j'admire l'élégance du second, je pense que vous devez retirer le *. Dans l'état actuel des choses, il supprimera toutes les lignes vides suivant une ligne qui se termine par un >. … Hmm. En repensant à la question, je vois que c'est un peu ambigu. La question dit: «Je veux supprimer tous les retours à la ligne qui se produisent après un >,…» J'interprète cela comme signifiant que cela >\n\n\n\n\nfoodevrait être changé en \n\n\n\nfoo, mais je suppose que cela foopourrait être la sortie souhaitée.
Scott
@Scott - J'ai testé avec des variations sur les éléments suivants: printf '>\n>\n\n>>\n>\n>>>\n>\nf\n\nff\n>\n' | tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'- qui se traduit >>>>>>>>>>f\n\nff\n\npour moi avec la première réponse. Je suis curieux de savoir ce que vous faites pour le casser, car j'aimerais le réparer. Quant au deuxième point - je ne suis pas d'accord pour dire qu'il est ambigu. L'OP ne demande pas de supprimer tout ce qui > précède une ligne \nélectronique, mais plutôt de supprimer toutes les \n lignes électroniques suivant a >.
mikeserv
1
Oui, mais une interprétation valable est que, dans >\n\n\n\n\n, seul le premier retour à la ligne est après a >; tous les autres suivent d'autres nouveautés. Notez que la suggestion du PO «c'est ce que je veux, si seulement cela fonctionnait» ne l'était sed -e 's/>\n/>/g'pas sed -e 's/>\n*/>/g'.
Scott
1
@Scott - la suggestion n'a pas fonctionné et n'a jamais pu. Je ne crois pas que la suggestion de code d'une personne qui ne comprend pas complètement le code puisse être considérée comme un point d'interprétation valable comme le langage simple que cette personne utilise également. Et d' ailleurs, la sortie - si elle a effectivement travaillé - de s/>\n/>/sur >\n\n\n\n\nserait encore quelque chose qui s/>\n/>/modifier Would.
mikeserv