Comment grep pour des groupes de n chiffres, mais pas plus que n?

33

J'apprends Linux et j'ai un défi que je n'arrive pas à résoudre seul. C'est ici:

grep une ligne d'un fichier qui contient 4 nombres dans une ligne mais pas plus de 4.

Je ne sais pas comment aborder cela. Je peux rechercher des nombres spécifiques mais pas leur montant dans une chaîne.

Bouddha
la source
2
Une ligne comme celle-ci doit-elle 1234a12345être affichée ou non?
Eliah Kagan
@ Bouddha, vous devez expliquer votre question avec un exemple.
Avinash Raj
si les nombres sont précédés par un espace ou par le début de l'ancre de ligne et suivis par un espace ou par la fin de l'ancre de ligne, vous pouvez simplement utiliser les limites de mot. \b\d{4}\b
Avinash Raj
1
Cette question diffère de certaines questions sur les expressions régulières en étant explicitement sur l' utilisation de grep . Les questions sur l'utilisation des utilitaires Unix dans Ubuntu, telles que grep, sed et awk, ont toujours été considérées comme satisfaisantes ici. Parfois, les gens demandent comment faire un travail avec le mauvais outil; alors le manque de contexte est un gros problème, mais ce n'est pas ce qui se passe ici. C'est un sujet suffisamment clair pour qu'on puisse y répondre utilement, utile pour notre communauté, et il n'y a aucun avantage à empêcher d'autres réponses ou à les pousser vers la suppression ou la migration. Je vote pour le rouvrir.
Eliah Kagan
1
Merci beaucoup les gars, je ne savais pas que j'aurais autant de retours. Voici la réponse que je cherchais: grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])'. La commande doit être capable de tirer une chaîne comme celle-ci (ce qu'elle fait): abc1234abcd99999
Buddha

Réponses:

52

Il y a deux façons d'interpréter cette question. Je vais aborder les deux cas. Vous voudrez peut-être afficher des lignes:

  1. qui contiennent une séquence de quatre chiffres qui ne fait pas partie d'une séquence de chiffres plus longue, ou
  2. qui contient une séquence de quatre chiffres mais plus une séquence de chiffres (même pas séparément).

Par exemple, (1) devrait s'afficher 1234a56789, mais pas (2).


Si vous souhaitez afficher toutes les lignes contenant une séquence de quatre chiffres qui ne fait pas partie d'une séquence de chiffres plus longue, procédez comme suit:

grep -P '(?<!\d)\d{4}(?!\d)' file

Cela utilise des expressions régulières Perl , que Ubuntu grep( GNU grep ) prend en charge via -P. Il ne correspond pas à texte comme 12345, ni ne correspond au 1234ou 2345qui font partie de celui - ci. Mais elle correspondra 1234à 1234a56789.

Dans les expressions rationnelles Perl:

  • \dsignifie n'importe quel chiffre (c'est un moyen court de dire [0-9]ou [[:digit:]]).
  • x{4}correspond x4 fois. (La { }syntaxe n'est pas spécifique aux expressions rationnelles Perl; elle s'applique grep -Eaussi bien aux expressions rationnelles étendues .) Ainsi, \d{4}est identique à \d\d\d\d.
  • (?<!\d)est une assertion de recherche négative de largeur nulle. Cela signifie "à moins d'être précédé de \d".
  • (?!\d)est une assertion d'anticipation négative de largeur nulle. Cela signifie "sauf si suivi de \d".

(?<!\d)et (?!\d)ne correspond pas au texte en dehors de la séquence de quatre chiffres; au lieu de cela, ils empêcheront (lorsqu'ils sont utilisés ensemble) d'empêcher l'appariement d'une séquence de quatre chiffres s'il fait partie d'une séquence de chiffres plus longue.

Utiliser uniquement le regard en arrière ou juste en avant est insuffisant, car la sous-séquence à quatre chiffres la plus à droite ou la plus à gauche serait toujours appariée.

L'un des avantages des assertions d'anticipation et d'anticipation est que votre modèle correspond uniquement aux séquences à quatre chiffres elles-mêmes, et non au texte environnant. Ceci est utile lorsque vous utilisez la mise en surbrillance des couleurs (avec l' --coloroption).

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

Par défaut, dans Ubuntu, chaque utilisateur a alias grep='grep --color=auto'son ~.bashrcfichier . Ainsi, la couleur est surlignée automatiquement lorsque vous exécutez une commande simple commençant par grep(c'est-à-dire lorsque les alias sont développés) et la sortie standard est un terminal (c'est ce que vérifie). Les allumettes sont généralement surlignées en rouge (proche du vermillon ), mais je les ai montrées en italiques gras. Voici une capture d'écran:--color=auto
Capture d'écran montrant cette commande grep, avec 12345abc789d0123e4 en sortie, avec le 0123 mis en surbrillance en rouge.

Et vous pouvez même grepimprimer uniquement le texte correspondant, et non la ligne entière, avec -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

Manière alternative, sans assertions de Look-Behind and Look-Ahead

Cependant, si vous:

  1. besoin d'une commande qui s'exécutera également sur des systèmes où grepne prend pas en charge -Pou ne souhaite pas utiliser une expression régulière Perl, et
  2. vous n'avez pas besoin de faire correspondre spécifiquement les quatre chiffres - ce qui est généralement le cas si votre objectif est simplement d'afficher des lignes contenant des correspondances, et
  3. sont d'accord avec une solution un peu moins élégante

... vous pouvez y parvenir avec une expression régulière étendue :

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Cela correspond à quatre chiffres et au caractère non numérique - ou au début ou à la fin de la ligne - qui les entoure. Plus précisément:

  • [0-9]correspond à un chiffre (comme [[:digit:]], ou \den Perl des expressions régulières) et {4}signifie « quatre fois ». Correspond donc à [0-9]{4}une séquence de quatre chiffres.
  • [^0-9]correspond aux caractères pas dans la plage de 0travers 9. Cela équivaut à [^[:digit:]](ou \D, dans les expressions rationnelles Perl).
  • ^, quand il n'apparaît pas [ ]entre parenthèses, correspond au début d'une ligne. De même, $correspond à la fin d'une ligne.
  • |les moyens ou et les parenthèses sont à regrouper (comme en algèbre). Correspond donc (^|[^0-9])au début de la ligne ou à un caractère non numérique, alors que ($|[^0-9])correspond à la fin de la ligne ou à un caractère non numérique.

Les correspondances ne se produisent donc que dans les lignes contenant une séquence à quatre chiffres ( [0-9]{4}) qui est simultanément:

  • au début de la ligne ou précédé d'un non-chiffre ( (^|[^0-9])), et
  • à la fin de la ligne ou suivi d’un non-chiffre ( ($|[^0-9])).

Si, en revanche, vous souhaitez afficher toutes les lignes contenant une séquence de quatre chiffres, mais ne contenant aucune séquence de plus de quatre chiffres (même une séquence distincte d'une autre séquence de quatre chiffres seulement), votre concept L’objectif est de trouver des lignes qui correspondent à un motif mais pas à un autre.

Par conséquent, même si vous savez comment faire avec un seul motif, je vous suggérerais d'utiliser quelque chose comme la deuxième suggestion de matt , greppour les deux motifs séparément.

Lorsque vous le faites, vous ne bénéficiez d'aucune des fonctionnalités avancées des expressions régulières Perl. Par conséquent, vous préférerez peut-être ne pas les utiliser. Mais en accord avec le style ci-dessus, voici un raccourcissement de la solution de matt en utilisant \d(et des accolades) à la place de [0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

Comme il utilise [0-9], la manière de mat est plus portable - il fonctionnera sur les systèmes où grepne prennent pas en charge les expressions régulières Perl. Si vous utilisez [0-9](ou [[:digit:]]) au lieu de \d, mais continuez à utiliser { }, vous obtenez la portabilité de la manière de matt un peu plus concise:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

Manière alternative, avec un motif simple

Si vous préférez vraiment une grepcommande qui

  1. utilise une seule expression rationnelle (pas deux greps séparés par un tuyau , comme ci-dessus)
  2. pour afficher des lignes contenant au moins une séquence de quatre chiffres,
  3. mais pas de séquences de cinq chiffres (ou plus),
  4. et cela ne vous dérange pas de faire correspondre toute la ligne, pas seulement les chiffres (cela ne vous dérange probablement pas)

... alors vous pouvez utiliser:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

L' -xindicateur fait en grepsorte que seules les lignes correspondant à la totalité de la ligne soient affichées (plutôt que toute ligne contenant une correspondance).

J'ai utilisé une expression régulière Perl parce que je pense que la brièveté \det l' \Daugmentation de la clarté dans le cas présent. Mais si vous avez besoin de quelque chose de portable pour des systèmes sur lesquels grepne prend pas en charge -P, vous pouvez les remplacer par [0-9]et [^0-9](ou avec [[:digit:]]et [^[:digit]]):

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

La façon dont ces expressions régulières fonctionnent est la suivante:

  • Au milieu \d{4}ou [0-9]{4}correspond à une séquence de quatre chiffres. Nous pouvons en avoir plusieurs, mais nous devons en avoir au moins un.

  • Sur la gauche, (\d{0,4}\D)*ou ([0-9]{0,4}[^0-9])*correspond à zéro ou plus ( *) instances de pas plus de quatre chiffres suivies d'un non-chiffre. Zéro chiffre (c'est-à-dire rien) est une possibilité pour "pas plus de quatre chiffres". Cela correspond (a) à la chaîne vide ou (b) à toute chaîne se terminant par un non-chiffre et ne contenant aucune séquence de plus de quatre chiffres.

    Etant donné que le texte situé immédiatement à gauche de la lettre centrale \d{4}(ou [0-9]{4}) doit être vide ou se terminer par un non-chiffre, cela empêche la centrale \d{4}de faire correspondre quatre chiffres comportant un autre (cinquième) chiffre juste à gauche d'eux.

  • À droite, (\D\d{0,4})*ou ([^0-9][0-9]{0,4})*correspond à zéro ou plusieurs *occurrences ( ) d'un non-chiffre suivi de quatre chiffres au maximum (qui, comme auparavant, pourrait être quatre, trois, deux, un, voire même aucun). Cela correspond (a) à la chaîne vide ou (b) à toute chaîne commençant par un non-chiffre et ne contenant aucune séquence de plus de quatre chiffres.

    Etant donné que le texte situé immédiatement à droite de la lettre centrale \d{4}(ou [0-9]{4}) doit être vide ou commencer par un non-chiffre, cela empêche la centrale \d{4}de faire correspondre quatre chiffres comportant un autre (cinquième) chiffre juste à droite d'eux.

Cela garantit qu'une séquence de quatre chiffres est présente quelque part et qu'aucune séquence de cinq chiffres ou plus n'est présente nulle part.

Ce n'est ni mauvais ni mauvais de le faire de cette façon. Mais peut-être que la raison la plus importante d’envisager cette alternative est qu’elle clarifie l’avantage de l’utilisation (ou similaire) à la place, comme suggéré ci-dessus et dans la réponse de matt .grep -P '\d{4}' file | grep -Pv '\d{5}'

De cette façon, il est clair que votre objectif est de sélectionner des lignes contenant une chose mais pas une autre. De plus, la syntaxe est plus simple (elle peut donc être comprise plus rapidement par de nombreux lecteurs / responsables).

Eliah Kagan
la source
9

Cela vous montrera 4 chiffres à la suite mais pas plus

grep '[0-9][0-9][0-9][0-9][^0-9]' file

Notez le ^ signifie pas

Il y a un problème avec ceci bien que je ne sois pas sûr de comment le réparer ... si le nombre est la fin de la ligne, alors il ne s'affichera pas.

Cette version plus laide cependant fonctionnerait pour ce cas

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]
mat
la source
oups, je n'ai pas besoin d'être egrep - je l'ai édité
matt
2
Le premier est faux - il trouve a12345b, car il correspond 2345b.
Volker Siegel
0

Si grepne supporte pas les expressions régulières perl ( -P), utilisez la commande shell suivante:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

printf '[0-9]%.0s' {1..4}va produire 4 fois [0-9]. Cette méthode est utile lorsque vous avez de longs chiffres et que vous ne voulez pas répéter le motif (remplacez-le simplement 4par votre nombre de chiffres à rechercher).

Utilisation -wcherchera les mots entiers. Toutefois, si vous êtes intéressé par les chaînes alphanumériques, telles que 1234a, ajoutez [^0-9]à la fin du motif, par exemple

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

L'utilisation $()est fondamentalement une substitution de commande . Consultez ce post pour voir comment se printfrépète le motif.

Kenorb
la source
0

Vous pouvez essayer la commande ci-dessous en remplaçant filepar le nom de fichier actuel dans votre système:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Vous pouvez également consulter ce tutoriel pour plus d'utilisations de la commande grep.

Mike Tyson
la source