Alternance / ou opérateur regex (foo | bar) dans GNU ou BSD Sed


28

Je n'arrive pas à le faire fonctionner. La documentation de GNU sed dit d'échapper au tuyau, mais cela ne fonctionne pas, pas plus que l'utilisation d'un tuyau droit sans l'échappement. L'ajout de parens ne fait aucune différence.

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat|dog/Bear/g'
cat
dog
pear
banana
cat
dog

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat\|dog/Bear/g'
cat
dog
pear
banana
cat
dog

Réponses:


33

Par défaut,sed utilise les expressions régulières de base POSIX , qui n'incluent pas l' |opérateur d'alternance. De nombreuses versions de sed, y compris GNU et FreeBSD, prennent en charge la commutation vers les expressions régulières étendues , qui incluent l' |alternance. La façon dont vous faites cela varie: GNU sed utilise-r , tandis que FreeBSD , NetBSD , OpenBSD et OS X sed utilisent -E. La plupart des autres versions ne le supportent pas du tout. Vous pouvez utiliser:

echo 'cat dog pear banana cat dog' | sed -E -e 's/cat|dog/Bear/g'

et cela fonctionnera sur ces systèmes BSD, et sed -ravec GNU.


GNU sedsemble avoir un support totalement non documenté mais fonctionnel -E, donc si vous avez un script multi-plateforme limité à ce qui précède, c'est votre meilleure option. Comme il n'est pas documenté, vous ne pouvez probablement pas vraiment compter dessus.

Un commentaire note que les versions BSD prennent -régalement en charge en tant qu'alias non documenté. OS X ne fonctionne toujours pas aujourd'hui et les anciennes machines NetBSD et OpenBSD auxquelles j'ai accès non plus, mais celle de NetBSD 6.1 le fait. Les Unités Commerciales que je peux atteindre universellement ne le font pas. Donc, avec tout cela, la question de la portabilité devient assez compliquée à ce stade, mais la réponse simple est de passer àawk si vous en avez besoin, qui utilise des ERE partout.


Les trois BSD que vous avez mentionnés prennent tous en charge l' -roption comme synonyme de -Ecompatibilité avec GNU sed. OpenBSD et OS X sed -Einterpréteront le tuyau échappé comme un tuyau littéral, et non comme un opérateur d'alternance. Voici un lien de travail vers la page de manuel NetBSD et en voici un pour OpenBSD qui n'a pas dix ans.
damien



9

Cela se produit car il (a|b)s'agit d'une expression régulière étendue, et non d'une expression régulière de base. Utilisez l' -Eoption pour y faire face.

echo 'cat
dog
pear
banana
cat
dog'|sed -E 's/cat|dog/Bear/g'

Depuis la sedpage de manuel:

 -E      Interpret regular expressions as extended (modern) regular
         expressions rather than basic regular expressions (BRE's).

Notez que -rc'est un autre indicateur pour la même chose, mais il -Eest plus portable et sera même dans la prochaine version des spécifications POSIX.


6

La manière portable de le faire - et la manière la plus efficace - est d'utiliser les adresses. Tu peux le faire:

printf %s\\n cat dog pear banana cat dog |
sed -e '/cat/!{/dog/!b' -e '};cBear'

De cette façon, si la ligne ne contient pas la chaîne cat et ne contient pas la chaîne dog sed b ranches hors du script, imprime automatiquement sa ligne actuelle et tire sur la ligne suivante pour commencer le cycle suivant. Par conséquent, il n'exécute pas l'instruction suivante - qui, dans cet exemple, csuspend la ligne entière pour lire Bear, mais il pourrait tout faire.

Il est probablement intéressant de noter également que toute déclaration suivante la !bdans cette sedcommande peut ne correspondre sur une ligne contenant soit la chaîne dogou cat- de sorte que vous pouvez effectuer d' autres tests sans danger de faire correspondre une ligne qui ne fonctionne pas - ce qui signifie que vous pouvez maintenant appliquer les règles à l'un ou à l'autre aussi.

Mais c'est la prochaine. Voici la sortie de la commande ci-dessus:

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

Vous pouvez également implémenter de manière portative une table de recherche avec des références arrières.

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ cat dog /;x
};G;s/^\(.*\)\n.* \1 .*/Bear/;P;d'

C'est beaucoup plus de travail à configurer pour ce cas d'exemple simple, mais cela peut rendre les sedscripts beaucoup plus flexibles à long terme.

Dans la première ligne, je xchange l'espace de maintien et l'espace de motif, puis insère le chien <space>chat à<space><space> cordes dans l'espace de maintien avant de les xchanger de nouveau.

À partir de là et sur chaque ligne suivante, je Gmaintiens l'espace ajouté à l'espace de motif, puis vérifie si tous les caractères depuis le début de la ligne jusqu'à ce que la nouvelle ligne que je viens d'ajouter à la fin correspondent à une chaîne entourée d'espaces après. Si c'est le cas, je remplace tout le lot par Bear et sinon il n'y a pas de mal parce que je Pn'imprime ensuite que jusqu'à la première nouvelle ligne apparaissant dans l'espace de motif, puis dje supprime tout cela.

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

Et quand je dis flexible, je le pense. Ici, il remplace chat avec Ours brun et chien avec Ours noir :

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ 1cat Brown 2dog Black /;x
};G;s/^\(.*\)\n.* [0-9]\1 \([^ ]*\) .*/\2Bear/;P;d'

###OUTPUT###
BrownBear
BlackBear
pear
banana
BrownBear
BlackBear

Vous pouvez bien sûr développer beaucoup le contenu de la table de recherche - j'ai repris l'idée des courriels usenet de Greg Ubben sur le sujet quand, dans les années 90, il a décrit comment il a construit une calculatrice brute à partir d'une seule sed s///déclaration.


1
ouf, +1. Vous avez un penchant pour sortir des sentiers battus, je dois dire
iruvar

@ 1_CR - Voir ma dernière édition - pas mon idée - ce qui ne veut pas dire que je n'apprécie pas cela et le considère comme un compliment. Mais j'aime rendre hommage quand c'est dû.
mikeserv

1

c'est une question assez ancienne, mais au cas où quelqu'un voudrait essayer, il y a un moyen d'effort assez faible pour le faire dans sed avec les fichiers sed. Chaque option peut être répertoriée sur une ligne distincte, et sed évaluera chacune. C'est un équivalent logique de ou. Par exemple, pour supprimer des lignes contenant un certain code:

tu peux dire : sed -E '/^\/\*!(40103|40101|40111).*\/;$/d'

ou mettez ceci dans votre fichier sed:

/^\/\*!40103.*\/;$/d
/^\/\*!40101.*\/;$/d
/^\/\*!40111.*\/;$/d

0

Voici une technique qui n'utilise aucune option spécifique à l'implémentation sed(par exemple -E, -r). Au lieu de décrire le modèle comme une seule expression régulière cat|dog, nous pouvons simplement l'exécuter seddeux fois:

echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat/Bear/g' | sed 's/dog/Bear/g'

C'est vraiment une solution de contournement évidente, mais qui mérite d'être partagée. Il se généralise naturellement à plus de deux chaînes de motif, bien qu'une très longue chaîne de sedne soit pas trop belle.

J'utilise souvent sed -i(qui fonctionne de la même manière dans toutes les implémentations) pour apporter des modifications aux fichiers. Ici, une longue liste de chaînes de modèle peut être bien incorporée, car chaque résultat temporaire est enregistré dans le fichier:

for pattern in cat dog owl; do
    sed -i "s/${pattern}/Bear/g" myfile
done
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.