web-dev-qa-db-fra.com

Comment remplacer plusieurs motifs à la fois par sed?

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

  • ab -> bc
  • bc -> ab

Si j'essaye deux remplace 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

EDIT: En fait, le texte pourrait avoir plus de 2 motifs et je ne sais pas combien de remplacements il me faudra. Comme il y avait une réponse disant que sed est un éditeur de flux et que ses remplacements se font avec avidité, je pense que je devrai utiliser un langage de script pour cela.

175
DaniloNC

Peut-être quelque chose comme ça:

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

Remplacez ~ par un caractère dont vous savez qu'il ne fera pas partie de la chaîne.

272
ooga

J'utilise toujours plusieurs déclarations 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

Ceci ajoutera un '\ n' avant tous les AND, GROUP BY, UNION et FROM, alors que '&' signifie la chaîne correspondante et '\ n &' signifie que vous souhaitez remplacer la chaîne correspondante par un '\ n' avant le 'correspondant '

Voici une variante de réponse de ooga qui fonctionne pour plusieurs paires de recherche et 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 désigne les limites du mot, 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 Word, cette technique peut ne pas fonctionner.

Notez également que cela donne les mêmes résultats que lorsque vous supprimez le s/________//g et que vous ajoutez && sed -i 's/________//g' path_to_your_files/*.txt à la fin de la commande, mais vous n'avez pas besoin de spécifier le chemin deux fois.

Une variante générale à ce sujet consisterait à utiliser \x0 ou _\x0_ à la place de ________ si vous savez qu'aucune valeur NULL n'apparaît dans vos fichiers, comme suggéré jthill .

10
Zack Morris

sed est un éditeur de flux. Il cherche 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/'

7
kuriouscoder

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 correspondance préparée et conservée dans l'espace de blocage (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 poursuivre la recherche sur toute la longueur de la ligne. Une fois que le marqueur a atteint la fin de la ligne, le processus est terminé et la table de recherche est imprimée et les marqueurs sont supprimés.

N.B. La table de consultation est préparée au tout début et un deuxième marqueur unique (dans ce cas :) est choisi de manière à 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
potong

Tcl a un construit pour cela

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

Cela fonctionne en parcourant la chaîne d'un caractère à la fois en effectuant 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
2
glenn jackman

Peut-être une approche plus simple pour une occurrence de modèle unique que vous pouvez essayer comme suit: 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 !!

1
dst_91

Voici un awk basé sur oogas sed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
0
Jotne