outil non orienté ligne pour le remplacement de chaîne?


13

J'ai récemment posé une question sur la façon de supprimer le caractère de nouvelle ligne s'il se produit après un autre caractère spécifique.

Les outils de traitement de texte Unix sont très puissants, mais presque tous traitent des lignes de texte, ce qui est très bien la plupart du temps lorsque l'entrée tient dans la mémoire disponible.

Mais que dois-je faire si je souhaite remplacer une séquence de texte dans un énorme fichier qui ne contient pas de retour à la ligne?

Par exemple, remplacer <foobar>par \n<foobar>sans lire l'entrée ligne par ligne? (car il n'y a qu'une seule ligne et il fait 2,5 G de long).


1
Êtes-vous prêt à utiliser perlou python?
iruvar

Perl va bien. Je viens de trouver gsar( home.online.no/~tjaberg ) que j'essaierai.
MattBianco

Réponses:


12

La première chose qui me vient à l'esprit face à ce type de problème est de changer le séparateur d'enregistrement. Dans la plupart des outils, ce paramètre est défini \npar défaut, mais il peut être modifié. Par exemple:

  1. Perl

    perl -0x3E -pe 's/<foobar>/\n$&/' file
    

    Explication

    • -0: ceci définit le séparateur d'enregistrement d'entrée sur un caractère étant donné sa valeur hexadécimale . Dans ce cas, je le mets à >la valeur hexadécimale 3E. Le format général est -0xHEX_VALUE. C'est juste une astuce pour briser la ligne en morceaux gérables.
    • -pe: imprime chaque ligne d'entrée après avoir appliqué le script donné par -e.
    • s/<foobar>/\n$&/: une simple substitution. C'est $&ce qui a été apparié, dans ce cas <foobar>.
  2. awk

    awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
    

    Explication

    • RS="<": définissez le séparateur d'enregistrement d'entrée sur >.
    • gsub(/foobar>/,"\n<foobar>"): remplacer tous les cas de foobar>avec \n<foobar>. Notez que parce que RSa été défini sur <, tous <sont supprimés du fichier d'entrée (c'est ainsi que cela awkfonctionne), nous devons donc faire correspondre foobar>(sans a <) et remplacer par \n<foobar>.
    • printf "%s",$0: affiche la "ligne" courante après la substitution. $0est le record actuel awk, il contiendra donc tout ce qui était avant le <.

J'ai testé ceux-ci sur un fichier à ligne unique de 2,3 Go créé avec ces commandes:

for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file

Tant le awket les perlutilisés quantités négligeables de mémoire.


Avez-vous déjà essayé Tie::File perldoc.perl.org/Tie/File.html . Je pense que ce sont les meilleures fonctionnalités Perllorsque l'on traite des fichiers volumineux.
cuonglm

@Gnouc J'ai joué un peu avec, oui. Mais i) l'OP a déjà professé une aversion pour Perl dans une autre question, donc je voulais rester simple ii) J'ai tendance à éviter d'utiliser des modules externes à moins que cela ne soit absolument nécessaire et iii) L'utilisation du module Tie :: File réduirait considérablement la syntaxe clair.
terdon

Se mettre d'accord. Une petite note qui Tie::Fileest un module de base depuis v5.7.3.
cuonglm

9

gsar (recherche générale et remplacement) est un outil très utile exactement à cette fin.

La plupart des réponses à cette question utilisent des outils basés sur des enregistrements et diverses astuces pour les adapter au problème, telles que la commutation du caractère de séparation d'enregistrements par défaut sur quelque chose supposé se produire assez fréquemment dans l'entrée pour ne pas rendre chaque enregistrement trop volumineux à gérer.

Dans de nombreux cas, c'est très fin et même lisible. Je fais comme des problèmes qui peuvent être facilement / efficacement résolus avec des outils partout, disponibles tels que awk, tr, sedet la bourne shell.

Effectuer une recherche binaire et remplacer dans un énorme fichier arbitraire avec un contenu aléatoire ne convient pas très bien à ces outils Unix standard.

Certains d'entre vous pensent peut-être que c'est de la triche, mais je ne vois pas comment utiliser le bon outil pour le travail peut être mauvais. Dans ce cas, il s'agit d'un programme C appelé gsarsous licence GPL v2 , donc cela me surprend un peu qu'il n'y ait pas de package pour cet outil très utile dans ni gentoo , redhat , ni ubuntu .

gsarutilise une variante binaire de l' algorithme de recherche de chaînes de Boyer-Moore .

L'utilisation est simple:

gsar -F '-s<foobar>' '-r:x0A<foobar>'

-Fsignifie mode "filtre", c'est-à-dire lecture / stdinécriture stdout. Il existe également des méthodes pour opérer sur les fichiers. -sspécifie la chaîne de recherche et -rle remplacement. La notation deux-points peut être utilisée pour spécifier des valeurs d'octets arbitraires.

Le mode insensible à la casse est pris en charge ( -i), mais les expressions régulières ne sont pas prises en charge , car l'algorithme utilise la longueur de la chaîne de recherche pour optimiser la recherche.

L'outil peut également être utilisé uniquement pour la recherche, un peu comme grep. gsar -brenvoie les décalages d'octets de la chaîne de recherche correspondante, et gsar -limprime le nom de fichier et le nombre de correspondances le cas échéant, un peu comme la combinaison grep -lavec wc.

L'outil a été écrit par Tormod Tjaberg (initial) et Hans Peter Verne (améliorations).


Si c'est sous GPL, envisageriez-vous de l'empaqueter pour une distribution :)
Rqomey

1
En fait, je pense sérieusement à faire un ebuild gentoo pour cela. Peut-être aussi un rpm. Mais je n'ai jamais construit de package .deb auparavant, j'espère donc que quelqu'un me battra dessus (car cela me prendra un certain temps).
MattBianco

Je doute que ce soit beaucoup de consolation, mais l'homebrew d'OS X a la formule pour gsar.
crazysim

5

Dans le cas étroit où la cible et les chaînes de remplacement sont de la même longueur, le mappage de la mémoire peut venir à la rescousse. Ceci est particulièrement utile si le remplacement doit être effectué sur place. Vous mappez essentiellement un fichier dans la mémoire virtuelle d'un processus, et l'espace d'adressage pour l'adressage 64 bits est énorme. Notez que le fichier n'est pas nécessairement mappé en une seule fois dans la mémoire physique , de sorte que les fichiers dont la taille de la mémoire physique disponible sur la machine peut être traitée plusieurs fois.

Voici un exemple Python qui remplace foobarparXXXXXX

#! /usr/bin/python
import mmap
import contextlib   
with open('test.file', 'r+') as f:
 with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
   pos = 0
   pos = m.find('foobar', pos)
   while pos > 0:
    m[pos: pos+len('XXXXXX')] = 'XXXXXX'
    pos = m.find('foobar', pos)

4

Il existe de nombreux outils pour cela:

ddest ce que vous voulez utiliser si vous voulez bloquer un fichier - ne lisez de manière fiable qu'un certain nombre d'octets qu'un certain nombre de fois. Il gère de manière portative le blocage et le déblocage des flux de fichiers:

tr -dc '[:graph:]' </dev/urandom | dd bs=32 count=1 cbs=8 conv=unblock,sync 2>/dev/null

###OUTPUT###

UI(#Q5\e BKX2?A:Z RAxGm:qv t!;/v!)N

J'utilise également trci-dessus car il peut gérer la conversion de tout octet ASCII en un autre (ou, dans ce cas, la suppression de tout octet ASCII qui n'est pas un caractère imprimable sans espace). C'est ce que j'ai utilisé en réponse à votre autre question ce matin, en fait, quand j'ai fait:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n' 

Il en existe de nombreux similaires . Cette liste devrait fournir un sous-ensemble de dénominateurs communs le plus bas avec lequel vous pourriez vous familiariser.

Mais, si je devais faire du traitement de texte sur 2,5 Go de fichier binaire, je pourrais commencer par od. Il peut vous donner un octal dumpou plusieurs autres formats. Vous pouvez spécifier toutes sortes d'options - mais je ne ferai qu'un octet par ligne dans un \Cformat d'échappement:

Les données que vous obtiendrez odseront régulières à n'importe quel intervalle que vous spécifiez - comme je le montre ci-dessous. Mais d'abord - voici une réponse à votre question:

printf 'first\nnewline\ttab spacefoobar\0null' |
od -A n -t c -v -w1 |
sed 's/^ \{1,3\}//;s/\\$/&&/;/ /bd
     /\\[0nt]/!{H;$!d};{:d
    x;s/\n//g}'

Ce petit peu au- dessus sur délimite \newlines, \0nulls, \tabs et <spaces>tout en préservant la \Cchaîne échappée pour le delimiter. Notez les fonctions Het xutilisées - à chaque fois qu'il sedrencontre un délimiteur, il échange le contenu de ses tampons de mémoire. De cette façon, sedne conserve que les informations nécessaires pour délimiter le fichier de manière fiable et ne succombe pas aux dépassements de tampon - ce n'est pas le cas, tant qu'il rencontre réellement ses délimiteurs. Tant qu'il le fera, sedcontinuera à traiter son entrée et odcontinuera à la fournir jusqu'à ce qu'elle rencontre EOF.

En l'état, sa sortie ressemble à ceci:

first
\nnewline
\ttab
 spacefoobar
\0null

Donc si je veux foobar:

printf ... | od ... | sed ... | 
sed 's/foobar/\
&\
/g'

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Maintenant, si vous voulez utiliser les Céchappements, c'est assez facile - car la seddouble \\barre oblique inversée a déjà échappé à toutes ses barres obliques inverses, donc printfexécutée depuis xargsn'aura aucun problème à produire la sortie selon vos spécifications. Mais xargs mange des guillemets shell , vous devrez donc le répéter:

printf 'nl\ntab\tspace foobarfoobar\0null' |
PIPELINE |
sed 's/./\\&/g' | 
xargs printf %b | 
cat -A

###OUTPUT###

nl$
tab^Ispace $
foobar$
$
foobar$
^@null%

Cela aurait pu tout aussi bien être enregistré dans une variable shell et sorti plus tard de la même manière. Le dernier sedinsère une \barre oblique inverse avant chaque caractère dans son entrée, et c'est tout.

Et voici à quoi tout cela ressemble avant de seds'en emparer:

printf 'nl\ntab\tspace foobarfoobar\0null' |
od -A n -t c -v -w1

   n
   l
  \n
   t
   a
   b
  \t
   s
   p
   a
   c
   e

   f
   o
   o
   b
   a
   r
   f
   o
   o
   b
   a
   r
  \0
   n
   u
   l
   l

2

Awk fonctionne sur des enregistrements successifs. Il peut utiliser n'importe quel caractère comme séparateur d'enregistrement (sauf l'octet nul sur de nombreuses implémentations). Certaines implémentations prennent en charge des expressions régulières arbitraires (ne correspondant pas à la chaîne vide) comme séparateur d'enregistrements, mais cela peut être compliqué car le séparateur d'enregistrements est tronqué à partir de la fin de chaque enregistrement avant d'être rangé dans $0(GNU awk définit la variable RTsur le séparateur d'enregistrements) qui a été retiré de la fin du record actuel). Notez que printtermine sa sortie avec le séparateur d'enregistrement de sortie ORSqui est une nouvelle ligne par défaut et défini indépendamment du séparateur d'enregistrement d'entrée RS.

awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'

Vous pouvez sélectionner efficacement un autre caractère comme séparateur d'enregistrement pour d' autres outils ( sort, sed...) en échangeant avec des sauts de ligne ce caractère avec tr.

tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'

De nombreux utilitaires de texte GNU prennent en charge l'utilisation d'un octet nul au lieu d'une nouvelle ligne comme séparateur.

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.