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.
Il y a deux façons d'interpréter cette question. Je vais aborder les deux cas. Vous voudrez peut-être afficher des lignes:
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:
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
Cependant, si vous:
grep
ne prend pas en charge -P
ou ne souhaite pas utiliser une expression régulière Perl, et ... 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:
(^|[^0-9])
), et ($|[^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 , grep
ing 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}'
Si vous préférez vraiment une commande grep
qui
grep
s séparés par un pipe , comme ci-dessus)... 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).
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]
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
où 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.
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])'