Grep: L'astérisque (*) ne fonctionne pas toujours

11

Si je grep un document qui contient les éléments suivants:

ThisExampleString

... pour l'expression This*Stringou *String, rien n'est retourné. Cependant, This*renvoie la ligne ci-dessus comme prévu.

Que l'expression soit placée entre guillemets ne fait aucune différence.

Je pensais que l'astérisque indiquait un certain nombre de caractères inconnus? Pourquoi ça marche seulement si c'est au début de l'expression? S'il s'agit d'un comportement voulu, que dois-je utiliser à la place des expressions This*Stringet *String?

Trae
la source
car ce n'est pas ainsi que fonctionnent les regex ... (en particulier * != any number of unknown characters
:.

Réponses:

18

Un astérisque dans les expressions régulières signifie "correspond à l'élément précédent 0 fois ou plus".

Dans votre cas particulier avec grep 'This*String' file.txt, vous essayez de dire: "Hé, grep, faites-moi correspondre le mot Thi, suivi de széro ou plusieurs fois en minuscule , suivi du mot String". Les minuscules sne se trouvent nulle part dans Example, donc grep ignore ThisExampleString.

Dans le cas de grep '*String' file.txt, vous dites "grep, correspond moi la chaîne vide - littéralement rien - précédant le mot String". Bien sûr, ce n'est pas comme ça ThisExampleStringqu'on doit lire. (Il existe d' autres significations possibles - vous pouvez essayer cela avec et sans le -Edrapeau - mais aucune des significations ne ressemble à ce que vous voulez vraiment ici.)

Sachant que .nous pourrions faire cela signifie « tout caractère unique »,: grep 'This.*String' file.txt. Maintenant, la commande grep le lira correctement: Thissuivi de n'importe quel caractère (pensez-y comme sélection de caractères ASCII) répété autant de fois, suivi de String.

Sergiy Kolodyazhnyy
la source
6
Dans Bash (et la plupart des shells Unix) *est un caractère spécial et il doit être cité ou échappé par exemple comme ceci: grep 'This*String' file.txtou ceci: grep This\*String file.txtpour ne pas être surpris par des résultats inattendus.
pabouk
2
@pabouk en coquilles, *c'est un caractère générique. Dans grep, *est un opérateur d'expression régulière. Voir unix.stackexchange.com/q/57957/70524
muru
11
pabouk a raison, l'expansion du nom de fichier a lieu avant l'exécution de la commande; comparer strace grep .* file.txt |& head -n 1 et strace grep '.*' file.txt |& head -n 1. Fonctionne grepégalement avec tous les caractères Unicode (par exemple, les echo -ne ⇏ | grep ⇏sorties )
kos
1
@Serg: vous avez une grande réputation ici, donc j'ai pensé que vous remarquiez immédiatement ce que je veux dire. L'OP a marqué la question bash donc je suppose que les commandes discutées sont interprétées par bash. Cela signifie que d'abord bashinterprète ses caractères spéciaux et seulement après toutes les extensions effectuées, il transmet les paramètres au processus généré. ----- Par exemple cette commande dans Bash: grep This.\*String file.txtpondra /bin/grepavec ces paramètres 0: grep1: This.*String2: file.txt. Notez que Bash a supprimé la barre oblique inverse et que l'échappement à l'origine a *été passé littéralement.
pabouk
7
La chose amusante (et pour le dépannage assez désagréable :) est que vos commandes comme grep This.*String file.txtfonctionneront normalement parce qu'il n'y aura probablement pas de fichier correspondant à l'expression générique du shell This.*String. Dans un tel cas par défaut, Bash passera l'argument littéralement *.
pabouk
8

Le *métacaractère dans BRE 1 s, ERE 1 s et PCRE 1 s correspond à 0 occurrence ou plus du modèle précédemment groupé (si un modèle groupé précède le *métacaractère), 0 ou plusieurs occurrences de la classe de caractères précédente (si une classe de caractères est précédant le *métacaractère) ou 0 ou plusieurs occurrences du caractère précédent (si ni un motif groupé ni une classe de caractères ne précède le *métacaractère);

Cela signifie que dans le This*Stringmodèle, étant le *métacaractère non précédé d'un modèle groupé ou d'une classe de caractères, le *métacaractère correspond à 0 occurrence ou plus du caractère précédent (dans ce cas, le scaractère):

% cat infile               
ThisExampleString
ThisString
ThissString
% grep 'This*String' infile
ThisString
ThissString

Pour faire correspondre 0 ou plusieurs occurrences de n'importe quel caractère, vous souhaitez faire correspondre 0 ou plusieurs occurrences du .métacaractère, qui correspond à n'importe quel caractère:

% cat infile               
ThisExampleString
% grep 'This.*String' infile
ThisExampleString

Le *métacaractère dans les BRE et les ERE est toujours "gourmand", c'est-à-dire qu'il correspondra à la correspondance la plus longue:

% cat infile
ThisExampleStringIsAString
% grep -o 'This.*String' infile
ThisExampleStringIsAString

Ce n'est peut-être pas le comportement souhaité; dans le cas contraire, vous pouvez activer le grepmoteur PCRE de (en utilisant l' -Poption) et ajouter le ?métacaractère qui, une fois placé après les métacaractères *et, +a pour effet de changer leur gourmandise:

% cat infile
ThisExampleStringIsAString
% grep -Po 'This.*?String' infile
ThisExampleString

1: Expressions régulières de base, expressions régulières étendues et expressions régulières compatibles Perl

kos
la source
Merci pour la réponse très instructive. Cependant, j'ai choisi une réponse différente car elle était plus courte et plus facile à comprendre. +1 pour avoir fourni autant de détails.
Trae
@Trae Vous êtes les bienvenus. C'est très bien, je suis d'accord que c'était peut-être trop complexe et faisait trop d'hypothèses pour quelqu'un qui ne connaissait pas trop le sujet.
kos
4

L'une des explications se trouve ici lien :

L'astérisque " *" ne signifie pas la même chose dans les expressions régulières que dans les caractères génériques; c'est un modificateur qui s'applique au caractère unique précédent ou à une expression telle que [0-9]. Un astérisque correspond à zéro ou plus de ce qui le précède. Correspond donc à [A-Z]*n'importe quel nombre de lettres majuscules, y compris aucune, tandis qu'il [A-Z][A-Z]*correspond à une ou plusieurs lettres majuscules.

Ova
la source
1

*a une signification particulière à la fois en tant que caractère de remplacement de shell ("caractère générique") et en tant que métacaractère d' expression régulière . Vous devez prendre en compte les deux, mais si vous citez votre expression régulière, vous pouvez empêcher le shell de le traiter spécialement et vous assurer qu'il passe inchangé à grep. Bien que sorte de semblable sur le plan conceptuel, ce *moyen de la coquille est tout à fait différent de ce que cela signifie grep.

Tout d'abord, le shell est traité *comme un caractère générique.

Tu as dit:

Que l'expression soit placée entre guillemets ne fait aucune différence.

Cela dépend des fichiers qui existent dans le répertoire dans lequel vous vous trouvez lorsque vous exécutez la commande. Pour les modèles qui contiennent le séparateur de répertoires /, cela peut dépendre des fichiers qui existent sur l'ensemble de votre système. Vous devez toujours citer les expressions régulières pour grep- et les guillemets simples sont généralement les meilleurs - à moins que vous ne soyez sûr d'être d'accord avec les neuf types de transformations potentiellement surprenantes que le shell effectue autrement avant d' exécuter la grepcommande.

Lorsque le shell rencontre un *caractère qui n'est pas entre guillemets , il prend pour signifier «zéro ou plus de n'importe quel caractère» et remplace le mot qui le contient par une liste de noms de fichiers qui correspondent au modèle. (Les noms de fichiers commençant par .sont exclus - sauf si votre modèle lui-même commence par . ou si vous avez configuré votre shell pour les inclure de toute façon.) Ceci est connu sous le nom de globbing - ainsi que sous les noms expansion de nom de fichier et expansion de nom de chemin .

L'effet avec grepsera généralement que le premier nom de fichier correspondant est considéré comme l'expression régulière - même s'il serait assez évident pour un lecteur humain qu'il ne s'agit pas d'une expression régulière - tandis que tous les autres noms de fichiers sont automatiquement répertoriés dans votre glob sont considérés comme les fichiers dans lesquels rechercher les correspondances. (Vous ne voyez pas la liste - elle est transmise de manière opaque à grep.) Vous ne voulez pratiquement jamais que cela se produise.

La raison pour laquelle ce n'est parfois pas un problème - et dans votre cas particulier, du moins jusqu'à présent , ce n'était pas le cas - est que *cela sera laissé seul si toutes les conditions suivantes sont vraies :

  1. Il n'y avait pas de fichiers dont les noms appariés. ... Ou vous avez désactivé le globbing dans votre shell, généralement avec set -fou l'équivalent set -o noglob. Mais c'est rare et vous savez probablement que vous l'avez fait.

  2. Vous utilisez un shell dont le comportement par défaut est de laisser *seul lorsqu'il n'y a aucun nom de fichier correspondant. C'est le cas dans Bash, que vous utilisez probablement , mais pas dans tous les shells de style Bourne. (Le comportement par défaut dans le shell populaire Zsh, par exemple, est que les globs (a) se développent ou (b) produisent une erreur.) ... Ou vous avez changé ce comportement de votre shell - la façon dont cela se fait varie à travers des coquilles.

  3. Vous n'avez pas autrement dit à votre shell d'autoriser le remplacement des globs par rien lorsqu'il n'y a pas de fichiers correspondants, ni d'échouer avec un message d'erreur dans cette situation. Dans Bash, cela aurait été fait en activant respectivement l' option shellnullglob ou .failglob

Vous pouvez parfois compter sur # 2 et # 3 mais vous pouvez rarement compter sur # 1. Une grepcommande avec un modèle non cité qui fonctionne maintenant peut cesser de fonctionner lorsque vous avez des fichiers différents ou lorsque vous l'exécutez à partir d'un endroit différent. Citez votre expression régulière et le problème disparaît.

Ensuite la grepcommande traite *comme un quantificateur.

Les autres réponses - comme celles de Sergiy Kolodyazhnyy et de kos - abordent également cet aspect de cette question, de manières quelque peu différentes. J'encourage donc ceux qui ne les ont pas encore lus à le faire, avant ou après avoir lu le reste de cette réponse.

En supposant que le *fait se rendre à grep - que la citation devrait garantir grep- signifie alors que l'élément qui le précède peut se produire un certain nombre de fois , plutôt que d'avoir à se produire exactement une fois . Cela pourrait encore se produire une fois. Ou il pourrait ne pas être présent du tout. Ou cela pourrait être répété. Le texte correspondant à l' une de ces possibilités sera mis en correspondance.

Qu'est-ce que je veux dire par «article»?

  • Un seul personnage . Depuis bmatchs un littéral b, b*correspond à zéro ou plus bs, ce qui ab*ccorrespond ac, abc, abbc, abbbc, etc.

    De même, étant donné .correspond à un caractère , .*correspond à zéro ou plusieurs caractères 1 , ainsi a.*cmatchs ac, akc, ahjglhdfjkdlgjdfkshlgc, même acccccchjckhcc, etc. Or

  • Une classe de personnages . Depuis [xy]matchs xou y, [xy]*correspond à zéro ou plusieurs caractères où chacun est soit xou y, ce qui p[xy]*qcorrespond pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyq, etc.

    Cela vaut aussi pour la sténographie formes de classes de personnages comme \w, \W, \set \S. Puisque \wcorrespond à n'importe quel caractère de mot, \w*correspond à zéro ou plusieurs caractères de mot. Ou

  • Un groupe . Depuis \(bar\)matchs bar, \(bar\)*matchs zéro ou plus bars, ce qui foo\(bar\)*bazcorrespond foobaz, foobarbaz, foobarbarbaz, foobarbarbarbaz, etc.

    Avec les options -Eou -P, greptraite votre expression régulière comme un ERE ou PCRE respectivement, plutôt que comme un BRE , puis les groupes sont entourés par ( )au lieu de \( \), vous utiliserez donc à la (bar)place de \(bar\)et à la foo(bar)bazplace de foo\(bar\)baz.

man grepdonne une explication raisonnablement accessible de la syntaxe BRE et ERE à la fin, ainsi qu'une liste de toutes les options de ligne de commande grepacceptées au début. Je recommande cette page de manuel comme ressource, ainsi que la documentation GNU Grep et ce site de tutoriel / référence (que j'ai lié à un certain nombre de pages ci-dessus).

Pour les tests et l'apprentissage grep, je recommande de l'appeler avec un modèle mais sans nom de fichier. Ensuite, il prend l'entrée de votre terminal. Entrez les lignes; les lignes qui vous sont renvoyées en écho sont celles qui contenaient le texte correspondant à votre motif. Pour quitter, appuyez sur Ctrl+ Dau début d'une ligne, qui signale la fin de l'entrée. (Ou vous pouvez appuyer sur Ctrl+ Ccomme avec la plupart des programmes en ligne de commande.) Par exemple:

grep 'This.*String'

Si vous utilisez l' --colorindicateur, grepmettra en évidence les parties spécifiques de vos lignes qui correspondent à votre expression régulière, ce qui est très utile à la fois pour déterminer ce que fait une expression régulière et pour trouver ce que vous recherchez une fois que vous l'avez fait. Par défaut, les utilisateurs d'Ubuntu ont un alias Bash qui provoque l' grep --color=autoexécution - ce qui est suffisant à cet effet - lorsque vous exécutez à greppartir de la ligne de commande, vous n'avez donc probablement même pas besoin de passer --colormanuellement.

1 Par conséquent, .*dans une expression régulière signifie ce que *signifie dans un glob shell. Cependant, la différence est qu'imprime grepautomatiquement les lignes qui contiennent votre correspondance n'importe où , il n'est donc généralement pas nécessaire d'avoir .*au début ou à la fin d'une expression régulière.

Eliah Kagan
la source