web-dev-qa-db-fra.com

Comment trouver et remplacer un caractère particulier, mais seulement s'il est entre guillemets?

Problème: J'ai des milliers de documents qui contiennent un caractère spécifique que je ne veux pas. Par exemple. le caractère a. Ces documents contiennent divers caractères, mais les a que je veux remplacer se trouvent entre guillemets ou simples. 

Je voudrais les trouver et les remplacer, et je pensais que l'utilisation de Regex serait nécessaire. J'utilise VSCode, mais je suis ouvert à toute suggestion. 

Ma tentative: J'ai pu trouver la regex suivante à faire correspondre pour une chaîne spécifique contenant les valeurs à l'intérieur du ().

".*?(r).*?"

Cependant, cela ne met en évidence que la citation complète. Je veux mettre en évidence le personnage seulement.

Toute solution, peut-être en dehors de regex, est la bienvenue.

Exemples de résultats: Étant donné que le caractère est a, recherchez le remplacement de b.

Somebody once told me "apples" are good for you => Somebody once told me "bpples" are good for you

"Aardvarks" make good kebabs => "Abrdvbrks" make good kebabs

The boy said "aaah!" when his mom told him he was eating aardvark => The boy said "bbbh!" when his mom told him he was eating aardvark

12
Ka Mok

Code Visual Studio

VS Code utilise le moteur JavaScript RegEx pour ses fonctionnalités de recherche/remplacement. Cela signifie que vous travaillez très peu avec regex par rapport à d'autres versions telles que .NET ou PCRE.

Assez chanceux pour que cette saveur prenne en charge l'apparence en tête et que vous en ayez la possibilité, vous pouvez utiliser chercher mais pas consommer caractère. Ainsi, une façon de s’assurer que nous sommes dans une chaîne entre guillemets consiste à rechercher le nombre de guillemets jusqu’au bas de la chaîne de fichier/sujet comme étant impair après la correspondance avec une variable a:

a(?=[^"]*"[^"]*(?:"[^"]*"[^"]*)*$)

Démo en direct

Cela ressemble à as dans une chaîne entre guillemets doubles, pour le remplacer par des chaînes entre guillemets simples, substituer tous "s par '. Vous ne pouvez pas avoir les deux à la fois.

L'expression regex ci-dessus pose cependant un problème: elle entre en conflit avec les guillemets d'échappement échappés au sein de chaînes entre guillemets doubles. Pour les égaler aussi si cela compte, il vous reste un long chemin à parcourir:

a(?=[^"\\]*(?:\\.[^"\\]*)*"[^"\\]*(?:\\.[^"\\]*)*(?:"[^"\\]*(?:\\.[^"\\]*)*"[^"\\]*(?:\\.[^"\\]*)*)*$)

L'application de ces approches sur des fichiers volumineux entraînera probablement un débordement de pile. Voyons donc une meilleure approche.

J'utilise VSCode, mais je suis ouvert à toute suggestion.

C'est génial. Ensuite, je suggérerais d'utiliser awk ou sed ou quelque chose de plus programmatique afin d'obtenir ce que vous voulez ou si vous êtes capable d'utiliser Sublime Text, il existe une chance de contourner ce problème de façon plus élégante.

Texte sublime

Ceci est supposé fonctionner sur des fichiers volumineux contenant des centaines de milliers de lignes, mais veillez à ce que cela fonctionne pour un seul caractère (ici a) qui, avec certaines modifications, peut également fonctionner pour un mot ou une sous-chaîne:

Rechercher:

(?:"|\G(?<!")(?!\A))(?<r>[^a"\\]*+(?>\\.[^a"\\]*)*+)\K(a|"(*SKIP)(*F))(?(?=((?&r)"))\3)
                           ^              ^            ^

Remplacez-le par: WHATEVER\3

Démo en direct

RegEx Breakdown:

(?: # Beginning of non-capturing group #1
    "   # Match a `"`
    |   # Or
    \G(?<!")(?!\A)  # Continue matching from last successful match
                    # It shouldn't start right after a `"`
)   # End of NCG #1
(?<r>   # Start of capturing group `r`
    [^a"\\]*+   # Match anything except `a`, `"` or a backslash (possessively)
    (?>\\.[^a"\\]*)*+   # Match an escaped character or 
                        # repeat last pattern as much as possible
)\K     # End of CG `r`, reset all consumed characters
(   # Start of CG #2 
    a   # Match literal `a`
    |   # Or
    "(*SKIP)(*F)    # Match a `"` and skip over current match
)
(?(?=   # Start a conditional cluster, assuming a positive lookahead
    ((?&r)")    # Start of CG #3, recurs CG `r` and match `"`
  )     # End of condition
  \3    # If conditional passed match CG #3
 )  # End of conditional

 enter image description here

Approche en trois étapes

Enfin et surtout...

Faire correspondre un caractère entre guillemets est délicat, car les délimiteurs sont exactement identiques, de sorte que les marques d'ouverture et de fermeture ne peuvent pas être distinguées sans regarder les chaînes adjacentes. Ce que vous pouvez faire est de changer un délimiteur en quelque chose d'autre afin que vous puissiez le rechercher plus tard.

Étape 1:

Rechercher: "[^"\\]*(?:\\.[^"\\]*)*"

Remplacer par: $0Я

Étape 2:

Rechercher: a(?=[^"\\]*(?:\\.[^"\\]*)*"Я)

Remplacez avec tout ce que vous attendez.

Étape 3:

Rechercher:

Remplacez par rien pour tout rétablir.


10
revo

Tout d'abord quelques considérations:

  1. Il peut y avoir plusieurs caractères a dans une citation unique.
  2. Chaque citation (utilisant des guillemets simples ou doubles) consiste en un caractère de citation initial, du texte et le caractère de citation final même. Une approche simple consiste à supposer que, lorsque les caractères de citation sont comptés de manière séquentielle, les impairs sont des guillemets ouverts et les pairs, des guillemets fermants.
  3. Après le point 2, il pourrait être utile de réfléchir davantage à la question de savoir si les chaînes entre guillemets doivent être autorisées. Voir l'exemple suivant: It's a shame 'this quoted text' isn't quoted. Ici, l'approche simple penserait qu'il existe deux chaînes entre guillemets: s a shame et isn. Un autre: This isn't a quote ...'this is' and 'it's unclear where this quote ends'. J'ai évité d'essayer de régler ces problèmes complexes et j'ai opté pour l'approche simple ci-dessous.

La mauvaise nouvelle est que le point 1 pose un problème, puisqu'un groupe de capture suivi d'un caractère générique répété (par exemple, (.*)*) ne capturera que la dernière "chose" capturée. Mais la bonne nouvelle est qu’il est possible de contourner ce problème dans certaines limites. De nombreux moteurs d'expression régulière autoriseront jusqu'à 99 groupes de capture (*). Donc, si nous pouvons supposer qu'il n'y aura pas plus de 99 as dans chaque citation (_ update _ ... ou même si nous ne pouvons pas - voir l'étape 3), nous pouvons faire le suivant...

(*) Malheureusement, mon premier port d'escale, Notepad ++ ne le permet pas - il ne permet que jusqu'à 9. Je ne suis pas sûr du code VS. Mais regex101 (utilisé pour les démonstrations en ligne ci-dessous) le fait.

TL; DR - Que faire?

  1. Rechercher: "([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*"
  2. Remplacer par: "\1\2\3\4\5\6\7\8\9\10\11\12\13\14\15\16\17\18\19\20\21\22\23\24\25\26\27\28\29\30\31\32\33\34\35\36\37\38\39\40\41\42\43\44\45\46\47\48\49\50\51\52\53\54\55\56\57\58\59\60\61\62\63\64\65\66\67\68\69\70\71\72\73\74\75\76\77\78\79\80\81\82\83\84\85\86\87\88\89\90\91\92\93\94\95\96\97\98\99"
  3. (Si vous le souhaitez, continuez à répéter les étapes précédentes s'il y a une possibilité de> 99 caractères de ce type entre guillemets jusqu'à ce qu'ils aient tous été remplacés). 
  4. Répétez l'étape 1 en remplaçant tout " par ' dans l'expression régulière, c'est-à-dire: '([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*'.
  5. Répétez les étapes 2-3.

Démos en ligne

Veuillez consulter les démos regex101 suivantes, qui pourraient en fait être utilisées pour effectuer les remplacements si vous êtes en mesure de copier le texte entier dans le contenu de "TEST STRING":

2
Steve Chambers
/(["'])(.*?)(a)(.*?\1)/g

Avec le motif de remplacement:

$1$2$4

Pour autant que je sache, VS Code utilise le même moteur de regex que JavaScript, c'est pourquoi j'ai écrit mon exemple en JS.

Le problème, c’est que si vous avez plusieurs guillemets dans un 1, vous aurez du mal à extraire les bonnes valeurs. Il doit donc y avoir une sorte de code derrière, ou vous, enfoncez le bouton Remplacer jusqu’à épuisement. des correspondances sont trouvées, pour récidiver le motif et se débarrasser de tous les a entre les guillemets

let regex = /(["'])(.*?)(a)(.*?\1)/g,
subst = `$1$2$4`,
str = `"a"
"helapke"
Not matched - aaaaaaa
"This is the way the world ends"
"Not with fire"
"ABBA"
"abba",
'I can haz cheezburger'
"This is not a match'
`;


// Loop to get rid of multiple a's in quotes
while(str.match(regex)){
    str = str.replace(regex, subst);
}

const result = str;
console.log(result);

1
Kyle Fairns

Si vous pouvez utiliser Visual Studio (au lieu de Visual Studio Code), il est écrit en C++ et en C # et utilise les expressions régulières .NET Framework , ce qui signifie que vous pouvez utiliser des recherches de longueur variable pour accomplir cela. 

(?<="[^"\n]*)a(?=[^"\n]*")

En ajoutant un peu plus de logique à l'expression régulière ci-dessus, nous pouvons lui dire d'ignorer les emplacements où une quantité égale de " le précède. Cela empêche les correspondances pour a en dehors des guillemets. Prenons, par exemple, la chaîne "a" a "a". Seules les première et dernière a de cette chaîne seront appariées, mais celle du milieu sera ignorée.

(?<!^[^"\n]*(?:(?:"[^"\n]*){2})+)(?<="[^"\n]*)a(?=[^"\n]*")

Maintenant, le seul problème est que cela va casser si nous avons échappé " entre deux guillemets doubles tels que "a\"" a "a". Nous devons ajouter plus de logique pour empêcher ce comportement. Heureusement, cette belle réponse existe pour une correspondance adéquate entre " échappé. En ajoutant cette logique à la regex ci-dessus, nous obtenons ce qui suit:

(?<!^[^"\n]*(?:(?:"(?:[^"\\\n]|\\.)*){2})+)(?<="[^"\n]*)a(?=[^"\n]*")

Je ne sais pas quelle méthode fonctionne le mieux avec vos chaînes, mais je vais expliquer cette dernière regex en détail, car elle explique également les deux précédentes.

  • (?<!^[^"\n]*(?:(?:"(?:[^"\\\n]|\\.)*){2})+) La recherche négative vérifie que ce qui précède ne correspond pas à ce qui suit
    • ^ Assert la position au début de la ligne
    • [^"\n]* Correspond à n'importe quoi sauf " ou \n n'importe quel nombre de fois
    • (?:(?:"(?:[^"\\\n]|\\.)*){2})+ Faites correspondre la ou les suivantes. Cela garantit que, s'il y a un " précédant le match, il s'agit de équilibré dans le sens où il y a un guillemet double en ouverture et en fermeture .
      • (?:"(?:[^"\\\n]|\\.)*){2} Faites correspondre le suivant exactement deux fois
      • " correspond à ceci littéralement
      • (?:[^"\\\n]|\\.)* Faites correspondre l’un des nombres suivants autant de fois que nécessaire
        • [^"\\\n] Correspond à n'importe quoi sauf ", \ et \n
        • \\. Correspond \ suivi de n'importe quel caractère
  • (?<="[^"\n]*) L'observation positive en veillant à ce qui précède correspond au suivant
    • " correspond à ceci littéralement
    • [^"\n]* Correspond à n'importe quoi sauf " ou \n n'importe quel nombre de fois
  • a Faites correspondre ceci littéralement
  • (?=[^"\n]*") Lookahead positif s'assurant que ce qui suit correspond au suivant
    • [^"\n]* Correspond à n'importe quoi sauf " ou \n n'importe quel nombre de fois
    • " correspond à ceci littéralement

Vous pouvez supprimer le \n du modèle ci-dessus comme suggéré ci-après. Je l'ai ajouté juste au cas où il y aurait une sorte de cas spécial que je n'envisage pas (c'est-à-dire des commentaires) qui pourrait briser cette regex dans votre texte. Le \A oblige également l'expression rationnelle à correspondre depuis le début de la chaîne (ou du fichier) au lieu du début de la ligne.

(?<!\A[^"]*(?:(?:"(?:[^"\\]|\\.)*){2})+)(?<="[^"]*)a(?=[^"]*")

Vous pouvez tester cette regex ici

Voici à quoi cela ressemble dans Visual Studio:

 Visual Studio example

1
ctwheels

I am using VSCode, but I'm open to any suggestions. 

Si vous souhaitez rester dans un environnement Editor, vous pouvez utiliser
Visual Studio (> = 2012) ou même notepad ++ pour une réparation rapide.
Cela évite d'avoir à utiliser un environnement de script parasite.

Ces deux moteurs (Dot-Net et Boost, respectivement) utilisent la construction \G.
Qui est commence le prochain match à la position où le dernier s'est arrêté

Encore une fois, ceci est juste une suggestion. 

Cette expression régulière ne vérifie pas la validité des citations équilibrées dans la totalité
chaîne à l'avance (mais cela pourrait se faire avec l'ajout d'une seule ligne). 

Il s’agit de savoir où sont les dedans et les dehors des guillemets. 

J'ai commenté la regex, mais si vous avez besoin de plus d'informations, faites le moi savoir.
Encore une fois, ceci n’est qu’une suggestion (je sais que votre éditeur utilise ECMAScript). 

Trouver (?s)(?:^([^"]*(?:"[^"a]*(?=")"[^"]*(?="))*"[^"a]*)|(?!^)\G)a([^"a]*(?:(?=a.*?")|(?:"[^"]*$|"[^"]*(?=")(?:"[^"a]*(?=")"[^"]*(?="))*"[^"a]*)))
Remplacer $1b$2 

C'est tout ce qu'on peut en dire. 

https://regex101.com/r/loLFYH/1

Commentaires 

(?s)                          # Dot-all inine modifier
 (?:
      ^                             # BOS 
      (                             # (1 start), Find first quote from BOS (written back)
           [^"]* 
           (?:                           # --- Cluster
                " [^"a]*                      # Inside quotes with no 'a'
                (?= " )
                " [^"]*                       # Between quotes, get up to next quote
                (?= " )
           )*                            # --- End cluster, 0 to many times

           " [^"a]*                      # Inside quotes, will be an 'a' ahead of here
                                         # to be sucked up by this match           
      )                             # (1 end)

   |                              # OR,

      (?! ^ )                       # Not-BOS 
      \G                            # Continue where left off from last match.
                                    # Must be an 'a' at this point
 )
 a                             # The 'a' to be replaced

 (                             # (2 start), Up to the next 'a' (to be written back)
      [^"a]* 
      (?:                           # --------------------
           (?= a .*? " )                 # If stopped before 'a', must be a quote ahead
        |                              # or,
           (?:                           # --------------------
                " [^"]* $                     # If stopped at a quote, check for EOS
             |                              # or, 
                " [^"]*                       # Between quotes, get up to next quote
                (?= " )

                (?:                           # --- Cluster
                     " [^"a]*                      # Inside quotes with no 'a'
                     (?= " )
                     " [^"]*                       # Between quotes 
                     (?= " )
                )*                            # --- End cluster, 0 to many times

                " [^"a]*                      # Inside quotes, will be an 'a' ahead of here
                                              # to be sucked up on the next match                    
           )                             # --------------------
      )                             # --------------------
 )                             # (2 end)
0
sln

Les "guillemets intérieurs" sont plutôt délicats, car il existe des scénarios compliqués à envisager pour automatiser complètement cette opération.

Quelles sont vos règles précises pour "inclus entre guillemets"? Avez-vous besoin de considérer des devis multi-lignes? Avez-vous cité des chaînes contenant des guillemets d'échappement ou des guillemets autres que des guillemets de début/fin? 

Cependant, il peut y avoir une expression assez simple pour faire beaucoup de ce que vous voulez.

Expression de recherche: ("[^a"]*)a

Expression de remplacement: $1b

Cela ne prend pas en compte à l'intérieur ou à l'extérieur des guillemets - vous devez le faire visuellement. Mais il met en surbrillance le texte de la citation au caractère correspondant, vous permettant ainsi de décider rapidement si cela est à l'intérieur ou non.

Si vous pouvez vivre avec l'inspection visuelle, nous pouvons alors créer ce modèle pour inclure différents types de devis, ainsi que des majuscules et des minuscules.

0
James