web-dev-qa-db-fra.com

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

Je n'arrive pas à le faire fonctionner. GNU sed dit d'échapper au tuyau, mais cela ne fonctionne pas, pas plus que d'utiliser 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
31
Gregg Leventhal

Par défaut sed utilise POSIX Basic Regular Expressions , qui n'inclut pas le | opérateur d'alternance. De nombreuses versions de sed, y compris GNU et FreeBSD, prennent en charge la commutation vers Expressions régulières étendues , qui incluent | alternance. La façon dont vous faites cela varie: GNU sed utilise -r , tandis que FreeBSD , NetBSD , OpenBSD et OS X sed utilisez -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 il fonctionnera sur ces systèmes BSD, et sed -r avec GNU.


GNU sed semble avoir un support totalement non documenté mais fonctionnel pour -E, donc si vous avez un script multiplateforme 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 en charge -r comme alias non documenté également. 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 basculer vers awk si vous en avez besoin, qui utilise des ERE partout.

36
Michael Homer

Cela se produit car (a|b) est une expression régulière étendue, pas une expression régulière de base. Utilisez le -E option pour y faire face.

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

Depuis la page de manuel sed:

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

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

9
Nidal

La façon 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 sedb se retire du script, imprime automatiquement sa ligne actuelle et passe à la suivante pour commencer le cycle suivant. Il n'exécute donc pas l'instruction suivante - qui dans cet exemple c suspend la ligne entière pour lire Bear mais il pourrait tout faire.

Il est probablement intéressant de noter également que toute déclaration suivant le !b en ce que la commande sed peut uniquement correspondre sur une ligne contenant soit la chaîne dog soit cat - afin que vous puissiez effectuer des tests supplémentaires sans danger de faire correspondre une ligne qui ne le fait pas - ce qui signifie que vous pouvez désormais appliquer des règles à l'un ou à l'autre également.

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 scripts sed beaucoup plus flexibles à long terme.

Dans la première ligne, je x change l'espace d'attente et l'espace de motif, puis insère la chaîne <space>chat<space>chien<space> dans l'espace d'attente avant que e x ne les remette en place.

A partir de là et sur chaque ligne suivante, je G et je garde 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é d'espaces après lui. Si c'est le cas, je remplace tout le lot par Bear et sinon, il n'y a pas de mal parce que je n'imprime P que jusqu'au premier apparition d'une nouvelle ligne dans l'espace de motif, puis d éliminez tout.

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

Et quand je dis flexible, je le pense. Ici, il remplace cat par BrownBear et chien avec Blackear :

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 sur le contenu de la table de recherche - j'ai repris l'idée de Greg Ubben's utilisez les e-mails sur le sujet quand, dans les années 90, il a décrit comment il a construit une calculatrice brute sur un seul sed s/// déclaration.

6
mikeserv

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:

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

ou mettez ceci dans votre fichier sed:

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

Voici une technique qui n'utilise aucune option spécifique à l'implémentation pour 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 exécuter sed deux 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 sed ne 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 intégré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
0
jmd_dk