Je veux utiliser sed
pour remplacer quoi que ce soit dans une chaîne entre le premier AB
et la première occurrence de AC
(inclus) avec XXX
.
Pour exemple, j'ai cette chaîne (cette chaîne est pour un test uniquement):
ssABteAstACABnnACss
et je voudrais une sortie similaire à ceci: ssXXXABnnACss
.
Je l'ai fait avec Perl
:
$ echo 'ssABteAstACABnnACss' | Perl -pe 's/AB.*?AC/XXX/'
ssXXXABnnACss
mais je veux l'implémenter avec sed
. Les éléments suivants (à l'aide de l'expression rationnelle compatible Perl) ne fonctionnent pas:
$ echo 'ssABteAstACABnnACss' | sed -re 's/AB.*?AC/XXX/'
ssXXXss
Les expressions régulières sed correspondent à la plus longue correspondance. Sed n'a pas d'équivalent de non gourmand.
De toute évidence, nous voulons faire correspondre
AB
,AC
,AC
Malheureusement, sed
ne peut pas faire # 2 - du moins pas pour une expression régulière à plusieurs caractères. Bien sûr, pour une expression régulière à un seul caractère telle que @
(ou même [123]
), nous pouvons faire [^@]*
ou [^123]*
. Nous pouvons donc contourner les limites de sed en modifiant toutes les occurrences de AC
en @
puis en recherchant
AB
,@
,@
comme ça:
sed 's/AC/@/g; s/AB[^@]*@/XXX/; s/@/AC/g'
La dernière partie modifie les instances inégalées de @
retour à AC
.
Mais, bien sûr, c'est une approche imprudente, car l'entrée peut déjà contenir @
caractères, donc, en les faisant correspondre, nous pourrions obtenir des faux positifs. Cependant, étant donné qu'aucune variable Shell n'aura jamais de NUL (\x00
), NUL est probablement un bon caractère à utiliser dans la solution de contournement ci-dessus au lieu de @
:
$ echo 'ssABteAstACABnnACss' | sed 's/AC/\x00/g; s/AB[^\x00]*\x00/XXX/; s/\x00/AC/g'
ssXXXABnnACss
L'utilisation de NUL nécessite GNU sed. (Pour s'assurer que GNU sont activées, l'utilisateur ne doit pas avoir défini la variable Shell POSIXLY_CORRECT.)
Si vous utilisez sed avec GNU's -z
flag pour gérer les entrées séparées par NUL, telles que la sortie de find ... -print0
, alors NUL ne sera pas dans l'espace de motif et NUL est un bon choix pour la substitution ici.
Bien que NUL ne puisse pas être dans une variable bash, il est possible de l'inclure dans une commande printf
. Si votre chaîne d'entrée peut contenir n'importe quel caractère, y compris NUL, alors voyez réponse de Stéphane Chazelas qui ajoute une méthode d'échappement intelligente.
Certaines implémentations sed
prennent en charge cela. ssed
a un mode PCRE:
ssed -R 's/AB.*?AC/XXX/g'
AT&T ast sed a conjonction et négation lors de l'utilisation regexps augmentée :
sed -A 's/AB(.*&(.*AC.*)!)AC/XXX/g'
De manière portable, vous pouvez utiliser cette technique: remplacez la chaîne de fin (ici AC
) par un seul caractère qui n'apparaît ni dans la chaîne de début ni dans la chaîne de fin (comme :
ici) pour que vous puissiez faire s/AB[^:]*://
, et si ce caractère peut apparaître dans l'entrée, utilisez un mécanisme d'échappement qui ne se heurte pas aux chaînes de début et de fin.
Un exemple:
sed 's/_/_u/g; # use _ as the escape character, escape it
s/:/_c/g; # escape our replacement character
s/AC/:/g; # replace the end string
s/AB[^:]*:/XXX/g; # actual replacement
s/:/AC/g; # restore the remaining end strings
s/_c/:/g; # revert escaping
s/_u/_/g'
Avec GNU sed
, une approche consiste à utiliser la nouvelle ligne comme caractère de remplacement. Comme sed
traite une ligne à la fois, la nouvelle ligne ne se produit jamais dans l'espace modèle , donc on peut faire:
sed 's/AC/\n/g;s/AB[^\n]*\n/XXX/g;s/\n/AC/g'
Cela ne fonctionne généralement pas avec d'autres implémentations sed
car elles ne prennent pas en charge [^\n]
. Avec GNU sed
, vous devez vous assurer que la compatibilité POSIX n'est pas activée (comme avec la variable d'environnement POSIXLY_CORRECT).
Non, les expressions rationnelles sed n'ont pas de correspondance non gourmande.
Vous pouvez faire correspondre tout le texte jusqu'à la première occurrence de AC
en utilisant "tout ce qui ne contient pas AC
" suivi de AC
, qui fait la même chose que Perl's .*?AC
. Le fait est que "tout ce qui ne contient pas AC
" ne peut pas être exprimé facilement comme une expression régulière: il y a toujours une expression régulière qui reconnaît la négation d'une expression régulière, mais l'expression rationnelle de négation se complique rapidement. Et dans sed portable, ce n'est pas possible du tout, car l'expression rationnelle de négation nécessite de grouper une alternance qui est présente dans les expressions régulières étendues (par exemple dans awk) mais pas dans les expressions régulières de base portables. Certaines versions de sed, telles que GNU sed, ont des extensions à BRE qui permettent d'exprimer toutes les expressions régulières possibles.
sed 's/AB\([^A]*\|A[^C]\)*A*AC/XXX/'
En raison de la difficulté de nier une expression régulière, cela ne se généralise pas bien. Ce que vous pouvez faire à la place est de transformer temporairement la ligne. Dans certaines implémentations sed, vous pouvez utiliser des sauts de ligne comme marqueur, car ils ne peuvent pas apparaître dans une ligne d'entrée (et si vous avez besoin de plusieurs marqueurs, utilisez le saut de ligne suivi d'un caractère variable).
sed -e 's/AC/\
&/g' -e 's/AB[^\
]*\nAC/XXX/' -e 's/\n//g'
Cependant, sachez que backslash-newline ne fonctionne pas dans un jeu de caractères avec certaines versions sed. En particulier, cela ne fonctionne pas dans GNU sed, qui est l'implémentation sed sur Linux non embarqué; dans GNU sed, vous pouvez utiliser \n
au lieu:
sed -e 's/AC/\
&/g' -e 's/AB[^\n]*\nAC/XXX/' -e 's/\n//g'
Dans ce cas précis, il suffit de remplacer le premier AC
par un saut de ligne. L'approche que j'ai présentée ci-dessus est plus générale.
Une approche plus puissante dans sed consiste à enregistrer la ligne dans l'espace d'attente, à supprimer tout sauf la première partie "intéressante" de la ligne, à échanger l'espace d'attente et l'espace modèle ou à ajouter l'espace modèle à l'espace d'attente et à répéter. Cependant, si vous commencez à faire des choses aussi compliquées, vous devriez vraiment penser à passer à awk. Awk n'a pas non plus de correspondance non gourmande, mais vous pouvez fractionner une chaîne et enregistrer les parties en variables.
sed - correspondance non gourmande par Christoph Sieghart
L'astuce pour obtenir une correspondance non gourmande dans sed est de faire correspondre tous les caractères à l'exception de celui qui met fin à la correspondance. Je sais, une évidence, mais j'ai perdu de précieuses minutes et les scripts Shell devraient, après tout, être rapides et faciles. Donc, au cas où quelqu'un d'autre en aurait besoin:Correspondance gourmande
% echo "<b>foo</b>bar" | sed 's/<.*>//g' bar
Correspondance non gourmande
% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g' foobar
Une alternative est de changer la chaîne pour que vous voulez la correspondance gourmande
echo "ssABtCeCAstACABnnACss" | rev | sed -E "s/(.*)CA.*BA(.*)/\1CA+-+-+-+-BA\2/" | rev
Utilisez rev
pour inverser la chaîne, inversez vos critères de correspondance, utilisez sed
de la manière habituelle, puis inversez le résultat ....
ssAB-+-+-+-+ACABnnACss
La solution est assez simple. .*
est gourmand, mais pas absolument gourmand. Pensez à faire correspondre ssABteAstACABnnACss
avec l'expression rationnelle AB.*AC
. Le AC
qui suit .*
doit avoir une correspondance. Le problème est que parce que .*
est gourmand, le AC
suivant correspondra au lastAC
plutôt qu'au premier. .*
mange le premier AC
tandis que le littéral AC
dans l'expression rationnelle correspond au dernier dans ssABteAstACABnn AC ss. Pour éviter que cela ne se produise, remplacez simplement le premier AC
par quelque chose ridicule pour le différencier du second et de toute autre chose.
echo ssABteAstACABnnACss | sed 's/AC/-foobar-/; s/AB.*-foobar-/XXX/'
ssXXXABnnACss
Le gourmand .*
va maintenant s'arrêter au pied de -foobar-
dans ssABteAst-foobar-ABnnACss
car il n'y a pas d'autre -foobar-
que cela -foobar-
, et l'expression rationnelle -foobar-
DOIT avoir une correspondance. Le problème précédent était que l'expression rationnelle AC
avait deux correspondances, mais parce que .*
était gourmand, la dernière correspondance pour AC
a été sélectionnée. Cependant, avec -foobar-
, une seule correspondance est possible, et cette correspondance prouve que .*
n'est pas absolument gourmand. L'arrêt de bus pour .*
se produit où il ne reste que n correspondance pour le reste de l'expression rationnelle suivant .*
.
Notez que cette solution échouera si un AC
apparaît avant le premier AB
car le mauvais AC
sera remplacé par -foobar-
. Par exemple, après la première substitution de sed
, ACssABteAstACABnnACss
devient -foobar-ssABteAstACABnnACss
; par conséquent, aucune correspondance ne peut être trouvée avec AB.*-foobar-
. Cependant, si la séquence est toujours ... AB ... AC ... AB ... AC ..., alors cette solution réussira.
Dans votre cas, vous pouvez simplement annuler le caractère de fermeture de cette façon:
echo 'ssABteAstACABnnACss' | sed 's/AB[^C]*AC/XXX/'