Comment remplacer plusieurs motifs à la fois par sed?


231

Supposons que j'ai une chaîne 'abbc' et que je souhaite remplacer:

  • ab -> bc
  • bc -> ab

Si j'essaye deux remplacements, le résultat n'est pas ce que je veux:

echo 'abbc' | sed 's/ab/bc/g;s/bc/ab/g'
abab

Quelle commande sed puis-je utiliser pour remplacer comme ci-dessous?

echo abbc | sed SED_COMMAND
bcab

EDIT : En fait, le texte pourrait avoir plus de 2 modèles et je ne sais pas combien de remplacements j'aurai besoin. Puisqu'il y a eu une réponse disant qu'il seds'agit d'un éditeur de flux et que ses remplacements sont avidement, je pense que je devrai utiliser un langage de script pour cela.


Devez-vous effectuer plusieurs remplacements sur la même ligne? Sinon, laissez simplement tomber le gdrapeau de ces deux s///commandes et cela fonctionnera.
Etan Reisner

Vous avez manqué le point de ma question. Je voulais dire que vous devez effectuer chaque remplacement plus d'une fois sur la même ligne. Y a-t-il plus d'une correspondance pour ab ou bc dans l'entrée d'origine.
Etan Reisner

Désolé @EtanReisner, j'ai mal compris, la réponse est oui. le texte peut avoir plusieurs remplacements.
DaniloNC

Réponses:


342

Peut-être quelque chose comme ça:

sed 's/ab/~~/g; s/bc/ab/g; s/~~/bc/g'

Remplacez-le ~par un caractère dont vous savez qu'il ne figurera pas dans la chaîne.


9
GNU sed gère nuls, vous pouvez donc l'utiliser \x0pour ~~.
2014

3
Est-ce gnécessaire et que fait-il?
Lee

12
@Lee gest pour global - il remplace toutes les instances du modèle dans chaque ligne, au lieu de la première (qui est le comportement par défaut).
naught101

1
Veuillez consulter ma réponse stackoverflow.com/a/41273117/539149 pour une variante de la réponse d'ooga qui peut remplacer plusieurs combinaisons simultanément.
Zack Morris

3
que vous savez ne sera pas dans la chaîne Pour le code de production, ne faites jamais d'hypothèse sur l'entrée. Pour les tests, eh bien, les tests ne prouvent jamais vraiment l'exactitude, mais une bonne idée pour un test est: Utilisez le script lui-même comme entrée.
hagello

33

J'utilise toujours plusieurs instructions avec "-e"

$ sed -e 's:AND:\n&:g' -e 's:GROUP BY:\n&:g' -e 's:UNION:\n&:g' -e 's:FROM:\n&:g' file > readable.sql

Cela ajoutera un «\ n» avant tous les AND, GROUP BY, UNION et FROM, tandis que «&» signifie la chaîne correspondante et «\ n &» signifie que vous souhaitez remplacer la chaîne correspondante par un «\ n» avant le «correspondant» "


14

Voici une variante de la réponse d'ooga qui fonctionne pour plusieurs recherches et remplace les paires sans avoir à vérifier comment les valeurs peuvent être réutilisées:

sed -i '
s/\bAB\b/________BC________/g
s/\bBC\b/________CD________/g
s/________//g
' path_to_your_files/*.txt

Voici un exemple:

avant:

some text AB some more text "BC" and more text.

après:

some text BC some more text "CD" and more text.

Notez que cela \bdénote les limites des mots, ce qui empêche l' ________interférence avec la recherche (j'utilise GNU sed 4.2.2 sur Ubuntu). Si vous n'utilisez pas de recherche de limite de mot, cette technique peut ne pas fonctionner.

Notez également que cela donne les mêmes résultats que la suppression de s/________//get l'ajout && sed -i 's/________//g' path_to_your_files/*.txtà la fin de la commande, mais ne nécessite pas de spécifier le chemin deux fois.

Une variation générale à ce sujet serait d'utiliser \x0ou _\x0_de remplacer ________si vous savez qu'aucune valeur nulle n'apparaît dans vos fichiers, comme l'a suggéré jthill .


Je suis d'accord avec le commentaire de hagello ci-dessus à propos de ne pas faire d'hypothèses sur ce que l'entrée peut contenir. Par conséquent, je pense personnellement que c'est la solution la plus fiable, en dehors de la tuyauterie seds les unes sur les autres ( sed 's/ab/xy/' | sed 's/cd/ab/' .....)
leetbacoon

12

sedest un éditeur de flux. Il recherche et remplace goulûment. La seule façon de faire ce que vous avez demandé est d'utiliser un modèle de substitution intermédiaire et de le modifier à la fin.

echo 'abcd' | sed -e 's/ab/xy/;s/cd/ab/;s/xy/cd/'


4

Cela pourrait fonctionner pour vous (GNU sed):

sed -r '1{x;s/^/:abbc:bcab/;x};G;s/^/\n/;:a;/\n\n/{P;d};s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/;ta;s/\n(.)/\1\n/;ta' file

Cela utilise une table de recherche qui est préparée et conservée dans l'espace d'attente (HS), puis ajoutée à chaque ligne. Un marqueur unique (dans ce cas \n) est ajouté au début de la ligne et utilisé comme méthode pour parcourir la recherche sur toute la longueur de la ligne. Une fois que le marqueur atteint la fin de la ligne, le processus est terminé et est imprimé la table de recherche et les marqueurs jetés.

NB La table de recherche est préparée au tout début et un deuxième marqueur unique (dans ce cas :) choisi pour ne pas entrer en conflit avec les chaînes de substitution.

Avec quelques commentaires:

sed -r '
  # initialize hold with :abbc:bcab
  1 {
    x
    s/^/:abbc:bcab/
    x
  }

  G        # append hold to patt (after a \n)

  s/^/\n/  # prepend a \n

  :a

  /\n\n/ {
    P      # print patt up to first \n
    d      # delete patt & start next cycle
  }

  s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/
  ta       # goto a if sub occurred

  s/\n(.)/\1\n/  # move one char past the first \n
  ta       # goto a if sub occurred
'

Le tableau fonctionne comme ceci:

   **   **   replacement
:abbc:bcab
 **   **     pattern

3

Peut être une approche plus simple pour une occurrence de modèle unique, vous pouvez essayer comme ci-dessous: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

Ma sortie:

 ~# echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'
 bcab

Pour plusieurs occurrences de motif:

sed 's/\(ab\)\(bc\)/\2\1/g'

Exemple

~# cat try.txt
abbc abbc abbc
bcab abbc bcab
abbc abbc bcab

~# sed 's/\(ab\)\(bc\)/\2\1/g' try.txt
bcab bcab bcab
bcab bcab bcab
bcab bcab bcab

J'espère que cela t'aides !!


2

Tcl a une fonction intégrée pour cela

$ tclsh
% string map {ab bc bc ab} abbc
bcab

Cela fonctionne en parcourant la chaîne un caractère à la fois en faisant des comparaisons de chaînes à partir de la position actuelle.

En perl:

perl -E '
    sub string_map {
        my ($str, %map) = @_;
        my $i = 0;
        while ($i < length $str) {
          KEYS:
            for my $key (keys %map) {
                if (substr($str, $i, length $key) eq $key) {
                    substr($str, $i, length $key) = $map{$key};
                    $i += length($map{$key}) - 1;
                    last KEYS;
                }
            }
            $i++;
        }
        return $str;
    }
    say string_map("abbc", "ab"=>"bc", "bc"=>"ab");
'
bcab

0

Voici un awkbasé sur les oogassed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
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.