web-dev-qa-db-fra.com

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

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.

30
Buddha

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 séparément).

Par exemple, (1) afficherait 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

Ceci utilise les expressions rationnelles Perl , que Ubuntu grep ( GNU grep ) prend en charge via -P. Cela ne correspond pas au texte comme 12345, ni au 1234 ou 2345 qui en fait partie. Mais cela correspondra au 1234 dans 1234a56789.

Dans les expressions rationnelles Perl:

  • \d signifie n'importe quel chiffre (c'est un moyen court de dire [0-9] ou [[:digit:]]).
  • x{4} correspond x 4 fois. (La syntaxe {} n'est pas spécifique aux expressions régulières Perl; elle s'applique également aux expressions régulières étendues via grep -E.) Donc, \d{4} est identique à \d\d\d\d.
  • (?<!\d) est une assertion de recherche négative de largeur nulle. Cela signifie "sauf si 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 correspondent 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 qu'une séquence de quatre chiffres soit mise en correspondance si elle fait partie d'une séquence de chiffres plus longue.

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

Un des avantages de l'utilisation 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'option --color).

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

Par défaut dans Ubuntu, chaque utilisateur a alias grep='grep --color=auto' dans son fichier ~.bashrc . Ainsi, les couleurs sont surlignées automatiquement lorsque vous exécutez une commande simple commençant par grep (c'est à ce moment-là que les alias sont développés) et la sortie standard est un terminal (c'est ce que --color=auto recherche). Les correspondances sont généralement surlignées en rouge (près de vermilion ), mais je l’ai montrée en gras et en italique. Voici une capture d'écran:
Screenshot showing that grep command, with 12345abc789d0123e4 as output, with the 0123 highlighted in red.

Et vous pouvez même faire en sorte que grep imprime uniquement le texte correspondant, et non toute la ligne, avec -o:

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

Manière alternative, sans assertions de regard en arrière et de regard en avant

Cependant, si vous:

  1. besoin d'une commande qui sera également exécutée sur des systèmes où grep ne prend pas en charge -P ou ne souhaite pas utiliser une expression régulière Perl, et
  2. il n'est pas nécessaire que les quatre chiffres correspondent précisément - 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

... alors 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 à n'importe quel chiffre (comme [[:digit:]] ou \d dans les expressions rationnelles Perl) et {4} signifie "quatre fois". Donc, [0-9]{4} correspond à une séquence de quatre chiffres.
  • [^0-9] fait correspondre les caractères non compris entre 0 et 9. Cela équivaut à [^[:digit:]] (ou \D, dans les expressions régulières Perl).
  • ^, lorsqu'il n'apparaît pas entre les []__, correspond au début d'une ligne. De même, $ correspond à la fin d'une ligne.
  • | signifie ou et les parenthèses sont destinées au groupement (comme en algèbre). Donc, (^|[^0-9]) correspond 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 se produisent donc uniquement 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 à quatre chiffres, mais ne contenant aucune séquence de plus de quatre chiffres (même celle qui est distincte d’une autre séquence de quatre chiffres seulement), alors votre but est conceptuellement de trouver des lignes qui correspondent à un modèle mais pas à un autre.

Par conséquent, même si vous savez le faire avec un seul motif, je vous suggérerais d'utiliser quelque chose comme la seconde suggestion de matt , greping pour 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. 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 en utilisant \d (et des accolades) au lieu de [0-9]:

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

Puisqu'il utilise [0-9], la manière dont matt est plus portable - cela fonctionnera sur les systèmes où grep ne prend pas en charge les expressions régulières Perl. Si vous utilisez [0-9] (ou [[:digit:]]) au lieu de \d, mais continuez d'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 commande grep qui

  1. utilise une seule expression régulière (et non deux greps séparés par un pipe , 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’indicateur -x fait que grep n’affiche que les lignes où toute la ligne correspond (plutôt que toute ligne contenant une correspondance).

J'ai utilisé une expression régulière Perl car je pense que la brièveté de \d et \D améliore considérablement la clarté dans ce cas. Mais si vous avez besoin de quelque chose de portable sur des systèmes où grep ne supporte pas -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 plusieurs instances (*) de quatre chiffres au plus, suivies d'un non-chiffre. Zéro chiffre (rien) est une possibilité pour "pas plus de quatre chiffres". Ceci correspond à (a) la chaîne vide ou (b) à toute chaîne se terminant par par un non-chiffre et ne contenant aucune séquence de plus de quatre chiffres.

    Étant donné que le texte situé immédiatement à gauche du \d{4} central (ou [0-9]{4}) doit être vide ou se terminer par un autre chiffre, cela empêche le \d{4} central de faire correspondre quatre chiffres comportant un autre (cinquième) chiffre juste à gauche de ceux-ci. .

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

    Le texte situé immédiatement à droite du \d{4} central (ou [0-9]{4}) devant être vide ou commençant par un autre chiffre, cela empêche le \d{4} central de faire correspondre quatre chiffres comportant un autre (cinquième) chiffre juste à droite.

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 de grep -P '\d{4}' file | grep -Pv '\d{5}' (ou similaire), comme suggéré ci-dessus et dans la réponse de matt .

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).

46
Eliah Kagan

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]
8
matt

Si grep ne prend pas en charge 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} produira 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 modèle (remplacez simplement 4 par votre nombre de chiffres à rechercher).

Utiliser -w cherchera les mots entiers. Toutefois, si vous êtes intéressé par les chaînes alphanumériques, telles que 1234a, ajoutez [^0-9] à la fin du modèle, par exemple.

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

Utiliser $() est fondamentalement un substitution de commande . Vérifiez ceci post pour voir comment printf répète le modèle.

0
kenorb

Vous pouvez essayer la commande ci-dessous en remplaçant le nom de fichier actuel dans votre système. Vous pouvez également vérifier ce tutoriel pour d'autres utilisations de la commande grep:

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

0
Mike Tyson