web-dev-qa-db-fra.com

Existe-t-il une application utilitaire de ligne de commande capable de rechercher et de remplacer un bloc de lignes spécifique dans un fichier texte?

UPDATE (voir fin de question)

Le texte "rechercher et remplacer" les programmes utilitaires que j'ai vus semble ne rechercher que ligne par ligne ...

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

Par exemple: le fichier de test contient-il ce exact group 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 le souhaite afin de pouvoir remplacer plusieurs lignes de texte dans un fichier et de savoir que je ne remplace pas les mauvaises lignes.

Je ne remplacerais jamais "The Jabberwocky" (Lewis Carroll), mais cela en fait un nouvel exemple :)

UPDATE:
.. (sous-mise à jour) Mon commentaire suivant sur les raisons lorsque n'utilise pas sed sont niquement ​​dans le contexte de; ne poussez aucun outil au-delà de son objectif (je l'utilise assez souvent et le considère comme étant inestimable.)

Je viens juste de trouver une page Web intéressante sur sed et quand ne pas l'utiliser.
Donc, à cause de toutes les réponses sed, je posterai le lien .. cela fait partie de sed FAQ sur sourceforge =

De plus, je suis presque sûr qu'il y a un moyen diff peut localiser le bloc de texte (une fois qu'il est localisé, le remplacement est assez simple; utilisez head et tail) ... ' diff 'vide toutes les données nécessaires, mais je ne sais pas encore comment les filtrer, ... (j'y travaille encore)

7
Peter.O

Ce simple script python devrait effectuer la tâche suivante:


#!/usr/bin/env python

# Syntax: 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, le fichier entier est mis en mémoire en une fois. Pour les petits fichiers texte, cela devrait cependant fonctionner assez bien.

7
loevborg

Approche 1: changer temporairement les nouvelles lignes en quelque chose d'autre

L'extrait de code suivant permute les nouvelles lignes avec des tubes, effectue le remplacement et remplace les séparateurs. L'utilitaire peut s'étouffer si la ligne qu'il voit est extrêmement longue. Vous pouvez choisir n'importe quel caractère avec lequel vous souhaitez échanger, à condition qu'il ne soit pas dans la chaîne de recherche.

<old.txt tr '\n' '|' |
sed 's/\(|\|^\)'\''Twas … toves|Did … Bandersnatch!'\''|/new line 1|new line 2|/g' |
tr '|' '\n' >new.txt

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

Awk et Perl prennent en charge deux ou plusieurs lignes vides en tant que séparateur d'enregistrement. Avec awk, passez -vRS= (vide RS variable). Avec Perl, transmettez -000 ("mode paragraphe") ou définissez $,="". Ce n'est pas utile ici cependant puisque vous avez une chaîne de recherche multi-paragraphes.

Awk et Perl supportent également la définition de n'importe quelle chaîne comme séparateur d'enregistrement. Définissez RS ou $, sur une chaîne ne figurant pas dans la chaîne de recherche.

<old.txt Perl -pe '
    BEGIN {$, = "|"}
    s/^'\''Twas … toves\nDid … Bandersnatch!'\''$/new line 1\nnew line 2/mg
' >new.txt

Approche 3: travail sur l'ensemble du dossier

Certains utilitaires vous permettent de lire et de travailler sur l’ensemble du fichier.

<old.txt Perl -0777 -pe '
    s/^'\''Twas … toves\nDid … Bandersnatch!'\''$/new line 1\nnew line 2/mg
' >new.txt

Approche 4: programme

Lisez les lignes une à une. Commencez avec un tampon vide. Si vous voyez la ligne "'Twas" et que le tampon est vide, mettez-le dans le tampon. Si vous voyez le "Did gyre" et qu'il y a une ligne dans la mémoire tampon, ajoutez la ligne en cours à la mémoire tampon, etc. Si vous venez d'ajouter la "ligne Bandersnatch", insérez le texte de remplacement. Si la ligne en cours n'est pas entrée dans la mémoire tampon, imprimez le contenu de la mémoire tampon, imprimez la ligne en cours et videz la mémoire tampon.

psusi montre une implémentation sed. Dans sed, le concept de tampon est intégré; ça s'appelle l'espace d'attente. Dans awk ou Perl, vous utiliseriez simplement une variable (peut-être deux, une pour le contenu du tampon et une pour le nombre de lignes).

3
Gilles

UPDATE: le script python de loevborg est certainement la solution la plus simple et la meilleure (cela ne fait aucun doute) et j'en suis très heureux, mais j'aimerais pour souligner que le script bash que j'ai présenté (à la fin de la question) est loin d'être aussi compliqué qu'il en a l'air .. J'ai découpé toutes les scories de débogage que j'avais l'habitude de tester .. et le voilà à nouveau sans surcharge. (pour tous ceux qui visitent cette page) .. C'est en gros un sed one-liner, avec des conversions avant et après hex:

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])}"
# delete the temp *.hex files.

Juste pour lancer mon chapeau dans le ring, j'ai mis au point une solution 'sed' qui ne rencontrera aucun problème avec les caractères spéciauxregex, car elle n'en utilise pas un. ! .. au lieu de cela cela fonctionne sur les versions Hexdumped des fichiers ...

Je pense que c'est trop "top heavy", mais cela fonctionne, et n'est apparemment pas limité par aucune limitation de taille. GNU sed a un nombre illimité de motifstaille de la mémoire tampon, et c'est là que se termine le bloc de lignes de recherche Hexdumped.

Je suis toujours à la recherche d'une diffsolution, car elle sera plus flexible en ce qui concerne les espaces (et je m'attendrais à ce qu'elle soit plus rapide) ... mais jusque-là .. C'est le fameux Mr Sed. :)

Ce script est entièrement exécuté tel quel et est raisonnablement commenté ...
Il semble plus gros qu'il ne l'est; J'ai seulement 7lignes de code essentiel.
Pour un test semi-réaliste, il télécharge le livre "Alice Through the Looking Glass" de Project Gutenberg (363.1 KB) ... et remplace le poème original de Jabberwocky par une ligne. version inversée de lui-même .. (intéressant, ce n'est pas très différent de le lire à l'envers :)

PS Je viens de me rendre compte que l’une des faiblesses de cette méthode est que votre fichier original utilise\r\n (0xODOA) en tant que nouvelle ligne et que votre "texte à rechercher" est enregistré avec\n (0x0A) .., ce processus de correspondance est alors terminé. l'eau ... ('diff' n'a pas de tels problèmes) ...


# In a text file, replace one block of lines with another block
#
# Keeping with the 'Jabberwocky' theme, 
#  and using 'sed' with 'hexdump', so 
#  there is no possible *special* char clash.
# 
# The current setup will replace only the first instance.
#   Using sed's 'g' command, it cah change all instances. 
#

  lookinglass="$HOME/Through the Looking-Glass by Lewis Carroll"
  jabberwocky="$lookinglass (jabberwocky)"
  ykcowrebbaj="$lookinglass (ykcowrebbaj)"

  ##### This section if FOR TEST PREPARATION ONLY
        fromURL="http://www.gutenberg.org/ebooks/12.txt.utf8"
        wget $fromURL -O "$lookinglass"
        if (($?==0))
        then  echo "Download OK"
        else  exit 1
        fi
        # Make a backup of the original (while testing)
        cp "$lookinglass" "$lookinglass(fromURL)"
        #
        # Extact the poem and write it to a file. (It runs from line 322-359)
        sed -n 322,359p "$lookinglass" > "$jabberwocky"
        cat "$jabberwocky"; read -p "This is the original.. (press Enter to continue)"
        #
        # Make a file containing a replacement block of lines
        tac "$jabberwocky" > "$ykcowrebbaj"
        cat "$ykcowrebbaj"; read -p "This is the REPLACEMENT.. (press Enter to continue)"
  ##### End TEST PREPARATION

# The main process
#
# Make 'hexdump' versions of the 3 files... source, expected, replacement 
  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"
# Now use 'sed' in a safe (no special chrs) way.
# Note, all files are now each, a single line  ('\n' is now '0A')
  sed -i "s/$(cat "$jabberwocky.xdig")/$(cat "$ykcowrebbaj.xdig")/p" "$lookinglass.xdig"

  ##### This section if FOR CHECKING THE RESULTS ONLY
        # Check result 1
        read -p "About to test for the presence of  'jabberwocky.xdig'  within itself (Enter) "
        sed -n "/$(cat "$jabberwocky.xdig")/p"     "$jabberwocky.xdig"
        echo -e "\n\nA dump above this line, means: 'jabberwocky' is as expected\n" 
        # Check result 2
        read -p "About to test for the presence of  'ykcowrebbaj.xdig'  within itself (Enter) "
        sed -n "/$(cat "$ykcowrebbaj.xdig")/p"     "$ykcowrebbaj.xdig"
        echo -e "\n\nA dump above this line, means: 'ykcowrebbaj' is as expected\n" 
        # Check result 3
        read -p "About to test for the presence of  'lookinglass.xdig'  within itself (Enter) "
        sed -n "/$(cat "$ykcowrebbaj.xdig")/p"     "$lookinglass.xdig"
        echo -e "\n\nA dump above this line, means: 'lookinglass' is as expected\n" 
        # Check result 4
        read -p "About to test for the presence of  'lookinglass.xdig'  within itself (Enter) "
        sed -n "/$(cat "$jabberwocky.xdig")/p"     "$lookinglass.xdig"
        echo -e "\n\nNo dump above this line means: 'lookinglass' is as expected\n"
  ##### End of CHECKING THE RESULTS

# Now convert the hexdump to binary, and overwrite the original
  cat "$lookinglass.xdig" | xxd -r -p > "$lookinglass"
# Echo the "modified" poem to the screen
  sed -n 322,359p "$lookinglass"
  echo -e "\n\nYou are now looking at the REPLACEMENT text (dumped directly from the source 'book'"
2
Peter.O

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

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

Sur cette base, 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 recrache:

tête de bocal

2
psusi

Même si vous n'aimez pas les sed et Perl de _ _ _ _ _ _ _ _ _ _ _ _ _ _ _, vous pouvez toujours trouver un goût pour awk de type gris. Cette réponse semble être ce que vous cherchez. Je le reproduis ici. Supposons que vous avez trois fichiers et que vous voulez remplacer needle par replacement dans haystack:


awk ' BEGIN { RS="" }
      FILENAME==ARGV[1] { s=$0 }
      FILENAME==ARGV[2] { r=$0 }
      FILENAME==ARGV[3] { sub(s,r) ; print }
    ' needle replacement haystack > output

Cela ne concerne pas les expressions régulières et prend en charge les caractères de nouvelle ligne. Il semble fonctionner avec des fichiers raisonnablement volumineux. Cela implique de mettre en mémoire le fichier entier en mémoire, de sorte que cela ne fonctionnera pas avec des fichiers de taille arbitraire. Si vous le souhaitez plus élégant, vous pouvez inclure le Shebang entier dans un script bash ou le transformer en script awk.

2
loevborg