Comment remplacer uniquement la Nième occurrence d'un motif dans un fichier?

10

Comment remplacer la troisième occurrence de la chaîne dans le fichier à l'aide de la sedcommande.

Exemple:

Changer la troisième occurrence de isà usdans le fichier.

Mon fichier d'entrée contient:

hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged.

Je m'attends à ce que la sortie soit:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.
Suresh Kumar
la source
3
L'entrée et la sortie sont identiques.
Hauke ​​Laging du
4
sedn'est pas le bon outil pour le travail.
choroba
@don_crissti Je l'ai corrigé. L'OP n'avait pas utilisé les outils de formatage (soit dit en passant, Sureshkumar, voir ici pour de l'aide sur l'édition de vos questions) et les éditeurs successifs avaient mal compris ce qui était recherché.
terdon

Réponses:

11

C'est beaucoup plus facile à faire perl.

Pour modifier la 3 ème occurrence:

perl -pe 's{is}{++$n == 3 ? "us" : $&}ge'

Pour changer toutes les 3 ème occurrence:

perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'
Stéphane Chazelas
la source
3

Lorsque la chaîne de remplacement ne se produit qu'une seule fois par ligne, vous pouvez combiner différents utilitaires.
Lorsque l'entrée est dans le fichier "entrée" et que vous remplacez "est" par "nous", vous pouvez utiliser

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'
Walter A
la source
Dans l'exemple de la question, il y en a plus d'une ispar ligne.
terdon
Je pensais que vous cherchiez "est" avec des espaces. Je pouvais éditer ma réponse avec la commande tr comme @jimmij utilisée, mais ma solution deviendrait bien inférieure à la sienne.
Walter A
Je ne suis pas le demandeur :). J'ai pensé la même chose, c'est pourquoi j'avais voté en faveur de votre réponse, mais si vous regardez la version originale de la question (cliquez sur le lien "Modifié il y a X minutes"), vous verrez que l'OP attendait que ce soit dans ce être changé en ainsi . Au fait, il n'y a pas besoin de chat là-bas.
terdon
2

Le script ci-dessous (utilisant la syntaxe GNU sed ) est utilisable pour l'édition sur place et non pour la sortie car il arrête les lignes d'impression après la substitution souhaitée:

sed -i '/is/{: 1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; q}' text.file

Si votre décision comme le choroba vous pouvez modifier ci-dessus pour

sed '/is/{:1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; :2 ; n ; $!b2}' text.file

qui sort toutes les lignes

Ou vous devez mettre toutes les lignes dans l'espace de motif (en mémoire, alors faites attention à la limitation de taille) et faites la substitution

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file
Costas
la source
2

Vous pouvez l'utiliser sedpour cela si auparavant des sauts de ligne sont remplacés par d'autres caractères, par exemple:

tr '\n' '\000' | sed 's/is/us/3' | tr '\000' '\n'

Et la même chose avec pure (GNU) sed:

sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'

( sedremplacement newline sans vergogne volé sur /programming//a/1252191/4488514 )

jimmij
la source
Si vous allez utiliser sedune syntaxe spécifique à GNU , vous pourriez aussi bien l'utiliser sed -z 's/is/us/3'.
Stéphane Chazelas
@ StéphaneChazelas -zdoit être une toute nouvelle fonctionnalité, mon GNU sed version 4.2.1ne sait rien de cette option.
jimmij
1
Ajouté dans 4.2.2 (2012). Dans votre deuxième solution, vous n'avez pas besoin de la conversion en \x0étape.
Stéphane Chazelas
Désolé pour l'édition. Je n'avais pas vu la version originale de la question et quelqu'un l'avait mal comprise et avait modifié la mauvaise ligne. Je suis revenu à la version précédente.
terdon
1
p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\}            \
     -e's/(\n*)(.*)/ \2 \1/'       \
     -e"s/is[$p]?[$s]/\n&/g"       \
     -e"s/([^$s])\n/\1/g;1G"       \
-e:c -e"s/\ni(.* )\n{3}/u\1/"      \
     -e"/\n$/!s/\n//g;/\ni/G"      \
     -e's//i/;//tc'                \
     -e's/^ (.*) /\1/;P;$d;N;D'

Ce morceau de sedtransporte juste un décompte des isoccurrences d'une ligne à la suivante. Il devrait gérer de manière fiable autant d' ises par ligne que vous y jetez, et il n'a pas besoin de mettre en mémoire tampon les anciennes lignes pendant qu'il le fait - il conserve juste un seul caractère de nouvelle ligne pour tout isce qu'il rencontre qui ne fait pas partie d'un autre mot.

Le résultat est qu'il ne modifiera que la troisième occurrence dans un fichier - et il portera des comptes par ligne. Donc, si un fichier ressemble à:

1. is is isis
2. is does

... il imprimera ...

1. is is isis
2. us does

Il gère d'abord les cas de bord en insérant un espace à la tête et à la queue de chaque ligne. Cela rend les limites des mots un peu plus faciles à déterminer.

Il recherche ensuite les ises valides en insérant une ligne \nélectronique avant que toutes les occurrences de iscelle-ci ne précèdent immédiatement zéro ou un caractère de ponctuation suivi d'un espace. Il fait un autre passage et supprime tous les \newlines qui sont immédiatement précédés d'un caractère non-espace. Ces marqueurs laissés correspondront is.et ismais pas thisou ?is.

Il rassemble ensuite chaque marqueur à la queue de la chaîne - pour chaque \nicorrespondance sur une ligne, il ajoute une ligne \nélectronique à la queue de la chaîne et la remplace par soit par iou u. S'il y a 3 \nlignes électroniques consécutives rassemblées à la fin de la chaîne, alors il utilise le u - sinon le i. La première fois que au est utilisé est également le dernier - le remplacement déclenche une boucle infinie qui se résume à get line, print line, get line, print line,et ainsi de suite.

À la fin de chaque cycle de boucle d'essai, il nettoie les espaces insérés, imprime uniquement jusqu'à la première nouvelle ligne apparaissant dans l'espace de motif et recommence.

Je vais ajouter une lcommande ook en tête de boucle comme:

l; s/\ni(.* )\n{9}/u\1/...

... et jetez un œil à ce qu'il fait car il fonctionne avec cette entrée:

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

... alors voici ce que ça fait:

 hai this \nis linux. \n$        #behind the scenes
hai this is linux.               #actually printed
 hai this \nis unix. \n\n$       #it builds the marker string
hai this is unix.
  \n\n\n$                        #only for lines matching the

  \n\n\n$                        #pattern - and not otherwise.

 hai this \nis mac. \n\n\n$      #here's the match - 3 ises so far in file.
hai this us mac.                 #printed
hai this is unchanged is.        #no look here - this line is never evaled

Cela a plus de sens peut-être avec plus d' ises par ligne:

nthword()(  p='[:punct:]' s='[:space:]'         
    sed -e '1!{/\n/!b' -e\}             \
        -e 's/\(\n*\)\(.*\)/ \2 \1/'    \
        -e "s/$1[$p]\{0,1\}[$s]/\n&/g"  \
        -e "s/\([^$s]\)\n/\1/g;1G;:c"   \
        -e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2\1/" \
        -e '/\n$/!s/\n//g;/\n'"$1/G"    \
        -e "s//$1/;//tc" -e 's/^ \(.*\) /\1/'     \
        -e 'P;$d;N;D'
)        

C'est pratiquement la même chose, mais écrit avec POSIX BRE et une gestion rudimentaire des arguments.

 printf 'is is. is? this is%.0s\n' {1..4}  | nthword is us 12

... obtient ...

is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is

... et si j'active ${dbg}:

printf 'is is. is? this is%.0s\n' {1..4}  | 
dbg=1 nthword is us 12

... nous pouvons le regarder itérer ...

 \nis \nis. \nis? this \nis \n$
 is \nis. \nis? this \nis \n\n$
 is is. \nis? this \nis \n\n\n$
 is is. is? this \nis \n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n\n\n\n\n$
is is. is? this us
is is. is? this is
mikeserv
la source
Saviez-vous que votre exemple dit "isis"?
flarn2006
@ flarn2006 - je suis presque sûr que c'est dit.
mikeserv
0

Voici une solution logique qui utilise sedet trdoit être écrite dans un script pour que cela fonctionne. Le code ci-dessous remplace chaque 3ème occurrence du mot spécifié dans la sedcommande. Remplacez i=3par i=npour que cela fonctionne pour tout n.

Code:

# replace new lines with '^' character to get everything onto a single line
tr '\n' '^' < input.txt > output.txt

# count number of occurrences of the word to be replaced
num=`grep -o "apple" "output.txt" | wc -l`

# in successive iterations, replace the i + (n-1)th occurrence
n=3
i=3
while [ $i -le $num ]
do
    sed -i '' "s/apple/lemon/${i}" 'output.txt'
    i=$(( i + (n-1) ))
done

# replace the '^' back to new line character
tr '^' '\n' < output.txt > tmp && mv tmp output.txt


Pourquoi cela fonctionne:

Supposons que le fichier texte soit a b b b b a c a d a b b b a b e b z b s b a b.

  • Lorsque n = 2: nous voulons remplacer chaque seconde occurrence de b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • Nous remplaçons d'abord la 2e occurrence, puis la 3e occurrence, puis les 4e, 5e, etc. Comptez dans l'ordre indiqué ci-dessus pour le constater par vous-même.
  • Lorsque n = 3: nous voulons remplacer chaque troisième occurrence de b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • Nous remplaçons d'abord la 3e occurrence, puis la 5e, puis les 7e, 9e, 11e, etc.
  • Lorsque n = 4: nous voulons remplacer chaque troisième occurrence de b.

    • Nous remplaçons d'abord la 4e occurrence, puis la 7e, puis la 10e, la 13e, etc.
agdhruv
la source