7 votes

Y a-t-il une application utilitaire en ligne de commande qui peut trouver un bloc spécifique de lignes dans un fichier texte et le remplacer?

MISE À JOUR (voir la fin de la question)

Les programmes utilitaires de recherche et de remplacement que j'ai vus semblent ne rechercher que ligne par ligne...

Existe-t-il un outil en ligne de commande qui peut localiser un bloc de lignes (dans un fichier texte) et le remplacer par un autre bloc de lignes ?

Par exemple : Le fichier de test contient-il ce groupe exact de lignes :

'Twas brillig, and the slithy toves
Did gyre and gimble in the wabe:
All mimsy were the borogoves,  
And the mome raths outgrabe. 

'Beware the Jabberwock, my son!
The jaws that bite, the claws that catch!
Beware the Jubjub bird, and shun
The frumious Bandersnatch!'

Je souhaite cela, pour pouvoir remplacer plusieurs lignes de texte dans un fichier et savoir que je ne suis pas en train d'écraser les mauvaises lignes.

Je ne remplacerais jamais "Le Jabberwocky" (Lewis Carroll), mais c'est un exemple original :)

MISE À JOUR:
..(sous-mise à jour) Mon commentaire suivant sur les raisons de ne pas utiliser sed est uniquement dans le contexte de : ne pas pousser un outil trop loin de son intention de conception (J'utilise souvent sed, et le considère comme inestimable.)

Je viens de trouver une page web intéressante sur sed et quand ne pas l'utiliser.
Donc, en raison de toutes les réponses sur sed, je vais poster le lien... il fait partie de la FAQ de sed sur sourceforge

Aussi, je suis assez sûr qu'il y a peut-être un moyen pour diff de faire le travail de localisation du bloc de texte (une fois qu'il est localisé, le remplacement est assez simple ; en utilisant head et tail) ... 'diff' renvoie toutes les données nécessaires, mais je n'ai pas encore trouvé comment les filtrer, ... (je travaille toujours dessus)

7voto

Donovan Woodside Points 1288

Ce simple script Python devrait faire l'affaire:

#!/usr/bin/env python

# Syntaxe: multiline-replace.py input.txt search.txt replacement.txt

import sys

inp = open(sys.argv[1]).read()
needle = open(sys.argv[2]).read()
replacement = open(sys.argv[3]).read()

sys.stdout.write(inp.replace(needle,replacement))

Comme la plupart des autres solutions, il a l'inconvénient que le fichier entier est lu en mémoire en une seule fois. Pour de petits fichiers texte, cela devrait tout de même fonctionner correctement.

3voto

Schof Points 952

Approche 1 : changer temporairement les sauts de ligne en autre chose

L'extrait suivant échange les sauts de ligne avec des barres verticales, effectue le remplacement, puis échange à nouveau les séparateurs. L'utilitaire risque d'échouer si la ligne qu'il voit est extrêmement longue. Vous pouvez choisir n'importe quel caractère à échanger tant qu'il ne se trouve pas dans votre chaîne de recherche.

new.txt

Approche 2 : changer le séparateur d'enregistrement de l'utilitaire

Awk et perl permettent de définir deux lignes ou plus vides comme séparateur d'enregistrement. Avec awk, specifiez -vRS= (variable RS vide). Avec Perl, passez -000 (mode “paragraphe”) ou définissez $,="". Ceci n'est pas utile ici étant donné que vous avez une chaîne de recherche multi-paragraphes.

Awk et perl permettent également de définir n'importe quelle chaîne comme séparateur d'enregistrement. Définissez RS ou $, comme n'importe quelle chaîne qui n'est pas dans votre chaîne de recherche.

new.txt

Approche 3 : travailler sur l'ensemble du fichier

Certains utilitaires vous permettent facilement de lire tout le fichier en mémoire et d'y travailler.

new.txt

Approche 4 : programme

Lire les lignes une par une. Commencez avec un tampon vide. Si vous voyez la ligne “'Twas” et que le tampon est vide, mettez-la dans le tampon. Si vous voyez la ligne “Did gyre” et qu'il y a une ligne dans le tampon, ajoutez la ligne actuelle au tampon, et ainsi de suite. Si vous venez d'ajouter la ligne “Bandersnatch”, affichez le texte de remplacement. Si la ligne actuelle n'est pas allée dans le tampon, affichez le contenu du tampon, affichez la ligne actuelle et videz le tampon.

psusi montre une implémentation sed. En sed, le concept de tampon est intégré ; c'est appelé l'espace de rétention. En awk ou perl, vous utiliseriez simplement une variable (peut-être deux, une pour le contenu du tampon et une pour le nombre de lignes).

2voto

Donovan Woodside Points 1288

Même si vous n'aimez pas sed et perl, vous pourriez quand même apprécier awk à la tempe grise. Cette réponse semble être ce que vous cherchez. Je la reproduis ici. Supposons que vous avez trois fichiers et que vous souhaitez remplacer aiguille par remplacement dans meule de foin:

awk ' BEGIN { RS="" }
      FILENAME==ARGV[1] { s=$0 }
      FILENAME==ARGV[2] { r=$0 }
      FILENAME==ARGV[3] { sub(s,r) ; print }
    ' aiguille remplacement meule de foin > sortie

Cela n'implique pas d'expressions régulières et prend en charge les caractères de nouvelle ligne. Cela semble fonctionner avec des fichiers de taille raisonnable. Cela implique de lire l'ensemble du fichier en mémoire, donc cela ne fonctionnera pas avec des fichiers de taille arbitraire. Si vous voulez que ce soit plus élégant, vous pouvez encapsuler tout le processus dans un script bash, ou le transformer en un script awk.

2voto

Peter Hilton Points 10580

MISE À JOUR: le script python de loevborg est certainement la solution la plus simple et la meilleure (il n'y a aucun doute à ce sujet) et je suis très satisfait, mais je tiens à souligner que le script bash que j'ai présenté (à la fin de la question) n'est pas aussi compliqué qu'il n'y paraît.. J'ai éliminé tout le bavardage de débogage que j'ai utilisé pour le tester.. et le voici à nouveau sans le fardeau inutile (pour quiconque visite cette page).. C'est essentiellement une commande en une ligne avec sed, avec des conversions hexadécimales pré et post- :

F=("$haystack"  "$needle"  "$replacement")
for f in "${F[@]}" ; do cat "$f" | hexdump -v -e '1/1 "%02x"' > "$f.hex" ; done
sed -i "s/$(cat "${F[1])}.hex")/$(cat "${F[2])}.hex")/p" "${F[0])}.hex"
cat "${F[0])}.hex" | xxd -r -p > "${F[0])}"
# supprimer les fichiers temporaires *.hex.

Juste pour apporter ma contribution, j'ai trouvé une solution avec 'sed' qui ne rencontrera aucun problème avec les caractères spéciaux, car elle n'en utilise pas un seul ! .. au lieu de cela, elle fonctionne sur des versions hexadécimales des fichiers...

Je trouve que c'est un peu trop "lourd", mais cela fonctionne, et n'est apparemment pas limité par des restrictions de taille.. GNU sed a une taille de tampon de modèle illimitée, et c'est là que se retrouve le bloc de lignes de recherche hexadécimal.. Donc c'est bien de ce côté-là...

Je cherche toujours une solution diff, car elle sera plus flexible concernant l'espace blanc (et je m'attends à ce qu'elle soit plus rapide)... mais en attendant.. C'est le célèbre M. Sed. :)

Ce script fonctionne entièrement tel quel, et est raisonnablement commenté...
Il semble plus grand qu'il ne l'est; il comporte seulement sept lignes de code essentielles.
Pour un test semi-réaliste, il télécharge le livre "Alice au Pays des Merveilles" depuis Project Gutenberg (363,1 Ko) ... et remplace le poème original du Jabberwocky par une version inversée.. (Curieusement, ce n'est pas très différent de le lire à l'envers :)

PS. Je viens de réaliser qu'une faiblesse de cette méthode est si votre original utilise \r\n (0xODOA) comme sa fin de ligne, et que votre "texte à rechercher" est enregistré avec \n (0x0A).. alors ce processus de recherche est voué à l'échec... ('diff' n'a pas de tels problèmes) ...


# Dans un fichier texte, remplacez un bloc de lignes par un autre bloc
#
# En restant sur le thème du 'Jabberwocky', 
#  et en utilisant 'sed' avec 'hexdump', afin qu'il n'y ait aucun risque de collision avec des caractères *spéciaux*.
# 
# La configuration actuelle ne remplacera que la première occurrence.
#   En utilisant la commande 'g' de sed, cela peut changer toutes les occurrences. 
#

  lookinglass="$HOME/De l'autre côté du miroir de Lewis Carroll"
  jabberwocky="$lookinglass (jabberwocky)"
  ykcowrebbaj="$lookinglass (ykcowrebbaj)"

  ##### Cette section EST UNIQUEMENT POUR LA PRÉPARATION DES TESTS
        fromURL="http://www.gutenberg.org/ebooks/12.txt.utf8"
        wget $fromURL -O "$lookinglass"
        if (($?==0))
        then  echo "Téléchargement OK"
        else  exit 1
        fi
        # Faire une sauvegarde de l'original (pendant les tests)
        cp "$lookinglass" "$lookinglass (depuisURL)"
        #
        # Extraire le poème et l'écrire dans un fichier. (Il s'exécute de la ligne 322-359)
        sed -n 322,359p "$lookinglass" > "$jabberwocky"
        cat "$jabberwocky"; read -p "Ceci est l'original.. (appuyez sur Entrée pour continuer)"
        #
        # Créer un fichier contenant un bloc de lignes de remplacement
        tac "$jabberwocky" > "$ykcowrebbaj"
        cat "$ykcowrebbaj"; read -p "Ceci est le REMPLACEMENT.. (appuyez sur Entrée pour continuer)"
  ##### Fin de la PRÉPARATION DES TESTS

# Le processus principal
#
# Créer des versions 'hexdump' des 3 fichiers... source, attendu, de remplacement 
  cat "$lookinglass" | hexdump -v -e '1/1 "%02x"' > "$lookinglass.xdig"
  cat "$jabberwocky" | hexdump -v -e '1/1 "%02x"' > "$jabberwocky.xdig"
  cat "$ykcowrebbaj" | hexdump -v -e '1/1 "%02x"' > "$ykcowrebbaj.xdig"
# Maintenant utiliser 'sed' de manière sûre (pas de caractères spéciaux).
# Remarque, tous les fichiers sont maintenant chacun, une seule ligne  ('\n' est maintenant '0A')
  sed -i "s/$(cat "$jabberwocky.xdig")/$(cat "$ykcowrebbaj.xdig")/p" "$lookinglass.xdig"

  ##### Cette section EST UNIQUEMENT POUR VÉRIFIER LES RÉSULTATS
        # Vérifier le résultat 1
        read -p "Sur le point de tester la présence de  'jabberwocky.xdig'  en lui-même (Entrée) "
        sed -n "/$(cat "$jabberwocky.xdig")/p"     "$jabberwocky.xdig"
        echo -e "\n\nUn affichage au-dessus de cette ligne signifie: 'jabberwocky' est comme prévu\n" 
        # Vérifier le résultat 2
        read -p "Sur le point de tester la présence de  'ykcowrebbaj.xdig'  en lui-même (Entrée) "
        sed -n "/$(cat "$ykcowrebbaj.xdig")/p"     "$ykcowrebbaj.xdig"
        echo -e "\n\nUn affichage au-dessus de cette ligne signifie: 'ykcowrebbaj' est comme prévu\n" 
        # Vérifier le résultat 3
        read -p "Sur le point de tester la présence de  'lookinglass.xdig'  en lui-même (Entrée) "
        sed -n "/$(cat "$ykcowrebbaj.xdig")/p"     "$lookinglass.xdig"
        echo -e "\n\nUn affichage au-dessus de cette ligne signifie: 'lookinglass' est comme prévu\n" 
        # Vérifier le résultat 4
        read -p "Sur le point de tester la présence de  'lookinglass.xdig'  en lui-même (Entrée) "
        sed -n "/$(cat "$jabberwocky.xdig")/p"     "$lookinglass.xdig"
        echo -e "\n\nAucun affichage au-dessus de cette ligne signifie: 'lookinglass' est comme prévu\n"
  ##### Fin de la VÉRIFICATION DES RÉSULTATS

# Convertir maintenant le hexdump en binaire, et écraser l'original
  cat "$lookinglass.xdig" | xxd -r -p > "$lookinglass"
# Afficher le poème "modifié" à l'écran
  sed -n 322,359p "$lookinglass"
  echo -e "\n\nVous regardez maintenant le texte DE REMPLACEMENT (extraite directement du 'livre' source"

2voto

psusi Points 35613

J'étais sûr qu'il devait y avoir un moyen de le faire avec sed. Après quelques recherches, je suis tombé sur ceci :

http://austinmatzko.com/2008/04/26/sed-multi-line-search-and-replace/

Basé sur cela, j'ai fini par écrire :

sed -n '1h;1!H;${;g;s/foo\nbar/jar\nhead/g;p;}' < x

Qui a correctement pris le contenu de x :

foo bar

Et a donné :

jar head

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X