Recherche de texte entre deux caractères ou chaînes spécifiques


17

Disons que j'ai des lignes comme celle-ci:

*[234]*
*[23]*
*[1453]*

*représente n'importe quelle chaîne (à l'exception d'une chaîne du formulaire [number]). Comment puis-je analyser ces lignes avec un utilitaire de ligne de commande et extraire le nombre entre crochets?

De manière plus générale, qui de ces outils cut, sed, grepou awkserait approprié pour une telle tâche?

Réponses:


16

Si vous avez GNU grep, vous pouvez utiliser son -ooption pour rechercher une expression régulière et ne produire que la partie correspondante. (Les autres implémentations grep ne peuvent afficher que la ligne entière.) S'il y a plusieurs correspondances sur une ligne, elles sont imprimées sur des lignes distinctes.

grep -o '\[[0-9]*\]'

Si vous ne voulez que les chiffres et non les crochets, c'est un peu plus difficile; vous devez utiliser une assertion de largeur nulle: une expression rationnelle qui correspond à la chaîne vide, mais uniquement si elle est précédée ou suivie, selon le cas, d'un crochet. Les assertions de largeur nulle ne sont disponibles qu'en syntaxe Perl.

grep -P -o '(?<=\[)[0-9]*(?=\])'

Avec sed, vous devez désactiver l'impression avec -n, faire correspondre la ligne entière et ne conserver que la partie correspondante. S'il y a plusieurs correspondances possibles sur une ligne, seule la dernière correspondance est imprimée. Voir Extraire une expression régulière correspondant à 'sed' sans imprimer les caractères environnants pour plus de détails sur l'utilisation de sed ici.

sed -n 's/^.*\(\[[0-9]*\]\).*/\1/p'

ou si vous ne voulez que les chiffres et non les crochets:

sed -n 's/^.*\[\([0-9]*\)\].*/\1/p'

Sans grep -o, Perl est l'outil de choix ici si vous voulez quelque chose à la fois simple et compréhensible. Sur chaque ligne ( -n), si la ligne contient une correspondance pour \[[0-9]*\], imprimez cette correspondance ( $&) et une nouvelle ligne ( -l).

perl -l -ne '/\[[0-9]*\]/ and print $&'

Si vous ne voulez que les chiffres, mettez des parenthèses dans l'expression régulière pour délimiter un groupe et imprimez uniquement ce groupe.

perl -l -ne '/\[([0-9]*)\]/ and print $1'

PS Si vous souhaitez uniquement exiger un ou plusieurs chiffres entre les crochets, changez [0-9]*en [0-9][0-9]*ou [0-9]+en Perl.


Très bien, à part cela, il veut "extraire le nombre entre parenthèses". Je pense que "sauf [number]" signifie sauf[0-9]
Peter.O

1
@ Peter.OI a compris que «sauf [nombre]» signifie qu'il n'y a pas d'autres parties de la ligne de ce formulaire. Mais j'ai modifié ma réponse pour montrer comment imprimer uniquement les chiffres, au cas où.
Gilles 'SO- arrête d'être méchant'

1
Ces perlaffirmations regex semblent vraiment utiles! J'ai lu à leur sujet après vous avoir vu utiliser des assertions en arrière et en avant, même dans grep (j'avais désactivé le fait que vous puissiez choisir un moteur d'expression régulière). Je vais consacrer un peu plus de temps à l'expression rationnelle de Perl à partir d'ici. Merci ... PS .. Je viens de lire man grep... "C'est très expérimental et grep -P peut avertir de fonctionnalités non implémentées." ... J'espère que cela ne signifie pas instable (?) ...
Peter.O

5

Vous ne pouvez pas le faire avec cut.

  1. tr -c -d '0123456789\012'
  2. sed 's/[^0-9]*//g'
  3. awk -F'[^0-9]+' '{ print $1$2$3 }'
  4. grep -o -E '[0-9]+'

tr est le plus naturel pour le problème et fonctionnerait probablement le plus rapidement, mais je pense que vous auriez besoin d'entrées gigantesques pour séparer l'une de ces options en termes de vitesse.


Pour sed, ^.*est gourmand et consomme tout sauf le dernier chiffre, et +doit être \+ou bien utiliser le posix \([0-9][0-9]*\).... et en tout cas 's/[^0-9]*//g'fonctionne aussi bien, ... Thanks for the par exemple tr -c`, mais n'est-ce pas \012superflu?
Peter.O

@Peter Merci d'avoir attrapé ça. J'aurais juré avoir testé l'exemple sed. :( Je l'ai changé pour votre version. En ce qui concerne \012: il est nécessaire sinon trmangera les nouvelles lignes.
Kyle Jones

Aha ... Je voyais comme \0, 1, 2(ou même \, 0, 1, 2). Je ne suis pas assez en phase avec l'octal semble-t-il .. Merci.
Peter.O

4

Si vous voulez dire extraire un ensemble de chiffres consécutifs entre des caractères non numériques, je suppose sedque ce awksont les meilleurs (bien qu'il grepsoit également en mesure de vous donner les caractères correspondants):

sed: vous pouvez bien sûr faire correspondre les chiffres, mais il est peut-être intéressant de faire le contraire, supprimez les non-chiffres (fonctionne dans la mesure où il n'y a qu'un seul numéro par ligne):

$ echo nn3334nn | sed -e 's/[^[[:digit:]]]*//g'
3344

grep: vous pouvez faire correspondre des chiffres consécutifs

$ echo nn3334nn | grep -o '[[:digit:]]*'
3344

Je ne donne pas d'exemple awkcar je n'en ai aucune expérience; il est intéressant de noter que, bien qu'il s'agisse d' sedun couteau suisse, grepvous offre un moyen plus simple et plus lisible de le faire, qui fonctionne également pour plus d'un numéro sur chaque ligne d'entrée (le -oseul imprime les parties correspondantes de l'entrée, chacune sur sa propre ligne):

$ echo dna42dna54dna | grep -o '[[:digit:]]*'
42
54

Tout comme une comparaison, voici une sedeqivalent du « plus d'un numéro par ligne » par exemple grep -o '[[:digit:]]*'. . . sed -nr '/[0-9]/{ s/^[^[0-9]*|[^0-9]*$//g; s/[^0-9]+/\n/g; p}'... (+1)
Peter.O

2

Puisqu'il a été dit que cela ne pouvait pas être fait cut, je montrerai qu'il est facilement possible de produire une solution qui n'est au moins pas pire que certaines des autres, même si je n'approuve pas l'utilisation de cutcomme la "meilleure" (ou même une solution particulièrement bonne). Il convient de dire que toute solution ne recherchant pas spécifiquement les chiffres *[et ]*autour d'eux fait des hypothèses simplificatrices et est donc sujette à l'échec sur des exemples plus complexes que ceux fournis par le demandeur (par exemple, les chiffres à l'extérieur *[et ]*, qui ne doivent pas être affichés). Cette solution vérifie au moins les parenthèses, et elle pourrait être étendue pour vérifier également les astérisques (laissés en exercice au lecteur):

cut -f 2 -d '[' myfile.txt | cut -f 1 -d ']'

Cela utilise l' -doption, qui spécifie un délimiteur. De toute évidence, vous pouvez également diriger l' cutexpression au lieu de lire un fichier. Bien que ce cutsoit probablement assez rapide, car il est simple (pas de moteur d'expression régulière), vous devez l'invoquer au moins deux fois (ou encore un peu de temps pour vérifier *), ce qui crée une surcharge de processus. Le seul réel avantage de cette solution est qu'elle est plutôt lisible, en particulier pour les utilisateurs occasionnels peu familiarisés avec les constructions d'expression régulière.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.