Comment remplacer plusieurs modèles à la fois avec sed?

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

  • ab - > bc
  • bc - > ab

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

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

Alors, quelle commande sed puis-je utiliser pour remplacer comme ci-dessous?

echo abbc | sed SED_COMMAND
bcab

Modifier : En fait, le texte pourrait avoir plus de 2 modèles et je ne sais pas combien de remplacements j'aurai besoin. Comme il y avait une réponse disant que sed est un éditeur de flux et que ses remplacements sont avidement, je pense que j'aurai besoin de pour utiliser un langage de script pour cela.

131
demandé sur Spencer Kormos 2014-10-26 04:30:12

7 réponses

Peut-être quelque chose comme ceci:

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

Remplacez ~ par un caractère que vous savez ne sera pas dans la chaîne.

210
répondu ooga 2014-10-26 01:41:07

Voici une variation sur la réponse de ooga {[12] } qui fonctionne pour plusieurs paires de recherche et de remplacement 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 \b indique les limites des mots, ce qui empêche le ________ d'interférer 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 travail.

Notez également que cela donne les mêmes résultats que la suppression du s/________//g et l'ajout de && 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 variante générale à ce sujet serait d'utiliser \x0 ou _\x0_ à la place de ________ Si vous savez qu'aucune valeur nulle n'apparaît dans vos fichiers, comme jthill l'a suggéré .

9
répondu Zack Morris 2017-05-23 11:47:26

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

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

4
répondu kuriouscoder 2014-10-26 06:56:59

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 maintenue dans L'espace de retenue (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 bump-le long de 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é sur la table de recherche et les marqueurs étant rejetés.

N.B. la table de recherche est préparé au tout début et un deuxième marqueur unique (dans ce cas :) choisi afin de 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
'

La table fonctionne comme ceci:

   **   **   replacement
:abbc:bcab
 **   **     pattern
4
répondu potong 2014-10-26 15:07:55

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 all AND's, GROUP BY's, UNION's et FROM, alors que' & ' signifie la chaîne correspondante et '\n& 'signifie que vous voulez remplacer la chaîne correspondante par un '\n 'avant le' matched '

2
répondu Paulo Henrique Lellis Gonalves 2018-08-27 14:11:48

Tcl a un intégré pour ce

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

Cela fonctionne en parcourant la chaîne d'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
1
répondu glenn jackman 2014-10-26 18:36:57

Voici un awk basé sur oogas sed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
-1
répondu Jotne 2014-10-26 07:42:48