514 votes

Correspondance non gourmande (réticente) de regex dans sed ?

J'essaie d'utiliser sed pour nettoyer des lignes d'URL afin d'extraire uniquement le domaine.

Donc de :

http://www.suepearson.co.uk/product/174/71/3816/

Je veux :

http://www.suepearson.co.uk/

(avec ou sans la barre oblique de fin, cela n'a pas d'importance)

J'ai essayé :

 sed 's|\(http:\/\/.*?\/\).*|\1|'

et (en échappant au quantificateur non grégaire)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

mais je ne parviens pas à obtenir le quantificateur non gourmand ( ? ) pour fonctionner, de sorte qu'il finit toujours par correspondre à la chaîne entière.

68 votes

Remarque : si vous délimitez vos regex avec "|", vous n'avez pas besoin d'échapper les "/". En fait, la plupart des gens délimitent avec "|" au lieu de "/" pour éviter les "piquets de clôture".

15 votes

@AttishOculus Le premier caractère après le 's' dans une expression de substitution dans sed est le délimiteur. Ainsi, 's^foo^bar^' ou 's!foo!bar!' fonctionnent également.

1 votes

Pour une regex étendue, utilisez sed -E 's... . Pourtant, pas d'opérateur réticent.

521voto

chaos Points 69029

Ni la regex Posix/GNU de base ni la regex Posix/GNU étendue ne reconnaissent le quantificateur non avide ; vous devez utiliser une regex ultérieure. Heureusement, la regex Perl pour ce contexte est assez facile à obtenir :

perl -pe 's|(http://.*?/).*|\1|'

20 votes

Pour le faire sur place, utilisez les options -pi -e .

16 votes

Holy smokes Je ne peux pas croire que ça a marché :-) La seule chose qui craint, c'est que maintenant mon script a une dépendance Perl :-( D'un autre côté, pratiquement toutes les distributions Linux ont déjà Perl, donc ce n'est probablement pas un problème :-)

10 votes

@Freedom_Ben : IIRC perl est requis par POSIX

344voto

Gumbo Points 279147

Dans ce cas précis, vous pouvez faire le travail sans utiliser une regex non gourmande.

Essayez cette regex non gourmande [^/]* au lieu de .*? :

sed 's|\(http://[^/]*/\).*|\1|g'

6 votes

Comment faire en sorte que la correspondance sed non gourmande d'une phrase utilise cette technique ?

6 votes

Malheureusement, vous ne pouvez pas ; voir réponse du chaos .

0 votes

Merci beaucoup ... puisque perl n'est plus dans la base d'installation par défaut dans de nombreuses distros linux !

154voto

stefanB Points 27796

Avec sed, j'implémente généralement une recherche non avide en cherchant tout ce qui n'est pas le séparateur jusqu'au séparateur :

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

Sortie :

http://www.suon.co.uk

c'est :

  • ne pas produire -n
  • recherche, correspondance de motifs, remplacement et impression s/<pattern>/<replace>/p
  • utiliser ; séparateur de commande de recherche au lieu de / pour faciliter la saisie des données s;<pattern>;<replace>;p
  • retenir la correspondance entre les parenthèses \( ... \) accessible par la suite avec \1 , \2 ...
  • match http://
  • suivi de tout ce qui est entre parenthèses [] , [ab/] signifierait soit a ou b ou /
  • premièrement ^ sur [] signifie not suivi de tout ce qui n'est pas la chose en question. []
  • donc [^/] signifie tout sauf / caractère
  • * est de répéter le groupe précédent afin [^/]* désigne les caractères sauf / .
  • jusqu'à présent sed -n 's;\(http://[^/]*\) signifie chercher et se souvenir http:// suivi de tous les caractères sauf / et souviens-toi de ce que tu as trouvé
  • nous voulons chercher jusqu'à la fin du domaine, alors arrêtez-vous sur le suivant. / alors ajoutez un autre / à la fin : sed -n 's;\(http://[^/]*\)/' mais nous voulons faire correspondre le reste de la ligne après le domaine, alors ajoutez .*
  • maintenant le match retenu dans le groupe 1 ( \1 ) est le domaine, il faut donc remplacer la ligne correspondante par les éléments enregistrés dans le groupe \1 et imprimer : sed -n 's;\(http://[^/]*\)/.*;\1;p'

Si vous souhaitez également inclure une barre oblique inverse après le domaine, ajoutez une barre oblique inverse supplémentaire dans le groupe à retenir :

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

sortie :

http://www.suon.co.uk/

9 votes

En ce qui concerne les modifications récentes : Les parenthèses sont une sorte de caractère de parenthèse, il n'est donc pas incorrect de les appeler parenthèses, surtout si vous suivez le mot avec les caractères réels, comme l'auteur l'a fait. De plus, c'est l'usage préféré dans certaines cultures, donc le remplacer par l'usage préféré dans votre propre culture semble un peu grossier, même si je suis sûr que ce n'est pas l'intention de l'éditeur. Personnellement, je pense qu'il est préférable d'utiliser des noms purement descriptifs tels que supports ronds , crochets et crochets d'angle .

3 votes

Est-il possible de remplacer le séparateur par une chaîne de caractères ?

41voto

andcoz Points 1341

Sed ne supporte pas l'opérateur "non gourmand".

Vous devez utiliser l'opérateur "[]" pour exclure "/" de la correspondance.

sed 's,\(http://[^/]*\)/.*,\1,'

P.S. Il n'est pas nécessaire de mettre la barre oblique inversée "/".

0 votes

Pas vraiment. si le délimiteur pouvait être l'un des nombreux caractères possibles (disons une chaîne de chiffres uniquement), votre correspondance par négation pourrait devenir de plus en plus complexe. c'est bien mais ce serait certainement bien d'avoir une option pour rendre .* non gourmand

1 votes

La question était plus générale. Ces solutions fonctionnent pour les URL mais pas (par exemple) pour mon cas d'utilisation consistant à supprimer les zéros de fin de ligne. s/([[:digit:]]\.[[1-9]]*)0*/\1/ ne fonctionnerait évidemment pas bien pour 1.20300 . Mais comme la question initiale portait sur les URL, elles devraient être mentionnées dans la réponse acceptée.

20voto

ishahak Points 141

Solution non gourmande pour plus d'un seul caractère

Ce fil est vraiment vieux mais je suppose que les gens en ont encore besoin. Disons que vous voulez tout tuer jusqu'à la toute première occurrence de HELLO . Vous ne pouvez pas dire [^HELLO] ...

Ainsi, une bonne solution implique deux étapes, en supposant que vous pouvez épargner un mot unique que vous n'attendez pas dans l'entrée, par exemple top_sekrit .

Dans ce cas, nous pouvons :

s/HELLO/top_sekrit/     #will only replace the very first occurrence
s/.*top_sekrit//        #kill everything till end of the first HELLO

Bien sûr, avec une entrée plus simple, vous pourriez utiliser un mot plus petit, ou peut-être même un seul caractère.

HTH !

4 votes

Pour le rendre encore meilleur, il est utile dans les situations où vous ne pouvez pas vous attendre à un caractère non utilisé : 1. remplacez ce caractère spécial par un MOT vraiment non utilisé, 2. remplacez la séquence finale par le caractère spécial, 3. effectuez la recherche se terminant par le caractère spécial, 4. replacez le caractère spécial, 5. replacez le MOT spécial. Par exemple, vous voulez un opérateur gourmand entre <hello> et </hello> :

3 votes

Here example: echo "Find:<hello>fir~st<br>yes</hello> <hello>sec~ond</hello>" | sed -e "s,~,VERYSPECIAL,g" -e "s,</hello>,~,g" -e "s,.*Find:<hello>([^~]*).*, \1 ," -e "s,\~,</hello>," -e "s,VERYSPECIAL,~,"

2 votes

Je suis d'accord. Belle solution. Je reformulerais le commentaire en disant : si vous ne pouvez pas compter sur le fait que ~ soit inutilisé, remplacez d'abord ses occurrences actuelles en utilisant s/~/VERYspeciaL/g, puis faites l'astuce ci-dessus, et enfin renvoyez le ~ original en utilisant s/VERYspeciaL/~/g.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X