N’imprimer qu’une partie d’un match avec grep

Je suis intéressé si je pourrais utiliser une seule commande grep pour la situation suivante.

J’ai un fichier dhcpd.conf dans lequel les hôtes DHCP sont définis. Étant donné le nom d’hôte, je dois trouver son adresse MAC dans le fichier dhcpd.conf. Je dois l’utiliser pour désactiver sa configuration de démarrage PXE, mais cela ne fait pas partie de cette question.

La syntaxe du fichier est uniforme, mais je veux quand même la rendre un peu infaillible. Voici comment sont définis les hôtes:

host client1 { hardware ethernet 12:23:34:56:78:89; fixed-address 192.168.1.11; filename "pxelinux.0"; } host client2 { hardware ethernet 23:34:45:56:67:78; fixed-address 192.168.1.12; filename "pxelinux.0"; } host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; filename "pxelinux.0"; } host client4 { hardware ethernet C1:CA:88:FA:F4:90; fixed-address 192.168.1.14; filename "pxelinux.0"; } 

Nous supposons que toutes les configurations ne prennent qu’une seule ligne, même si la syntaxe de dhcpd.conf permet de diviser les options en plusieurs lignes. Nous supposons cependant que l’ordre des options peut différer.

Je suis venu avec la commande grep suivante:

 grep -o "^[^#]*host.*${DHCP_HOSTNAME}.*hardware ethernet.*..:..:..:..:..:..;" /etc/dhcp/dhcpd-hosts.conf 

Il est censé ignorer les lignes commentées, autoriser les espaces blancs arbitraires entre les jetons et les faire correspondre jusqu’à la fin de l’adresse MAC. Quand je le lance, je reçois des lignes comme ceci:

 host client1 { hardware ethernet 12:23:34:56:78:89; 

C’est bien! Mais le fait est que je n’ai besoin que d’une adresse MAC, sans la corbeille précédente. Maintenant, je sais que l’utilisation d’un autre grep, ou couper, ou awk pour couper seulement l’adresse MAC de cette sortie serait sortingvial. Mais je me demande, est-il possible d’utiliser une seule commande grep pour obtenir le résultat final, sans avoir à diriger cette sortie vers un autre filtre? Evidemment je ne peux pas oublier le début du pattern, car je veux avoir un nom d’hôte spécifique, correspondant ainsi à ” ..:..:..:..:..:.. ” me donnerait tout le MAC adresses.

Encore une fois, je veux une seule commande (pas nécessairement grep) qui supprime uniquement l’adresse MAC correcte du fichier. Je ne suis donc pas intéressé par des solutions qui disent “grep … | grep …” ou “grep … | cut …”, etc.

Bien sûr, dans la pratique, rien de grave ne se produit si j’utilise plusieurs filtres et que je les achemine, je suis juste curieux de savoir s’il est possible de résoudre avec un seul filtre.

J’atsortingbuerais la sortie à une variable.

Vous pouvez utiliser un one-liner Perl pour faire correspondre chaque ligne du fichier avec une seule expression régulière avec un groupe de capture approprié, et pour chaque ligne correspondante, vous pouvez imprimer le sous-fichier.

Il existe plusieurs façons d’utiliser Perl pour cette tâche. Je suggère d’aller avec l’ perl -ne {program} , qui passe implicitement en boucle sur les lignes de stdin et exécute le programme un-liner une fois pour chaque ligne, la ligne actuelle étant disponible en tant que variable spéciale $_ . (Remarque: l’option -n n’imprime pas automatiquement la valeur finale de $_ à la fin de chaque itération de la boucle implicite, ce que ferait l’option -p , c’est-à-dire perl -pe {program} .)

Voici la solution. Notez que j’ai décidé de passer le nom d’hôte cible en utilisant l’option obscure -s , ce qui permet d’parsingr les spécifications d’affectation de variables après l’argument {program} , de la même manière que l’option -v d’awk. (Il n’est pas possible de passer des arguments de ligne de commande normaux avec l’option -n car la boucle implicite while (<>) { ... } englobe tous ces arguments pour les noms de fichiers, mais le mécanisme -s fournit une excellente solution. Voir Est-il possible de transmettre des arguments de ligne de commande à @ARGV lors de l’utilisation des options -n ou -p?. ) Cette conception évite d’avoir à intégrer la variable $DHCP_HOSTNAME dans la chaîne {program} elle-même, ce qui nous permet citez-le et enregistrez quelques barres obliques inverses (en fait 8).

 DHCP_HOSTNAME='client3'; perl -nse 'print($1) if m(^\s*host\s*$host\s*\{.*\bhardware\s*ethernet\s*(..:..:..:..:..:..));' -- -host="$DHCP_HOSTNAME"  

Je préfère souvent Perl à sed pour les raisons suivantes:

  • Perl fournit un environnement de programmation complet, alors que sed est plus limité.
  • Perl dispose d'un énorme référentiel de modules accessibles au public sur CPAN qui peuvent facilement être installés et utilisés avec l'option -M{module} . sed n'est pas extensible.
  • Perl dispose d'un moteur d'expression régulière beaucoup plus puissant que sed, avec des assertions de recherche, des verbes de contrôle de retour en arrière, du code Perl intra-regex et de remplacement, beaucoup plus d'options et d'échappements spéciaux, des options de groupes incorporés et plus. Voir perlre .
  • Contre-intuitivement, malgré sa plus grande sophistication, Perl est souvent beaucoup plus rapide que sed raison de son processus en deux passes et de son implémentation hautement optimisée de l'opcode. Voir http://rc3.org/2014/08/28/surprisingly-perl-outperforms-sed-and-awk/ par exemple.
  • Je trouve souvent que l'implémentation Perl équivalente est plus intuitive que celle de sed , puisque sed possède un ensemble de commandes plus primitif pour manipuler le texte sous-jacent.

Je choisirais sed pour cela, car vous pouvez utiliser une expression rationnelle pour l’adressage de ligne:

 sed -e "/host *${DHCP_HOSTNAME}/!d" -e "s/*.\(hardware [^;]*\).*/\1/g" 

La première expression supprime toutes les lignes ne correspondant pas à ${DHCP_HOSTNAME} (vous pourriez vouloir les ${DHCP_HOSTNAME} dans le shell si vous avez des méta-caractères d’expressions rationnelles dans vos noms d’hôtes, mais je suppose que vous n’en avez pas).

La deuxième expression correspond à la partie d’adresse matérielle et supprime le rest de la ligne.

Vous pouvez essayer Grep -o avec cette expression:

 grep -o "[0-9A-F]\{2\}:[0-9A-F]\{2\}:[0-9A-F]\{2\}:[0-9A-F]\{2\}:[0-9A-F]\{2\}:[0-9A-F]\{2\}" 

Sortie:

12: 23: 34: 56: 78: 89
23: 34: 45: 56: 67: 78
AB: CD: EF: 01: 23: 45
C1: CA: 88: FA: F4: 90

L’expression ci-dessus ne renverra que l’adresse MAC du fichier de configuration DHCP.

Étant donné que les gens répondent également avec différents outils, je pense que awk pourrait également être une bonne alternative.

 $ cat so host client1 { hardware ethernet 12:23:34:56:78:89; fixed-address 192.168.1.11; filename "pxelinux.0"; } host client2 { hardware ethernet 23:34:45:56:67:78; fixed-address 192.168.1.12; filename "pxelinux.0"; } #host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; filename "pxelinux.0"; } host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; filename "pxelinux.0"; } host client4 { hardware ethernet C1:CA:88:FA:F4:90; fixed-address 192.168.1.14; filename "pxelinux.0"; } $ awk '/^[^#]/ && /client3/ { printf ("%s: %s\n", $2, $6); }' so client3: AB:CD:EF:01:23:45; 

J’utilise une double correspondance pour exclure les lignes commentées et utiliser simplement l’index des champs pour imprimer les informations souhaitées. De cette façon, il devrait également être facile de supprimer la partie PXE. Par exemple, supprimer la directive filename pour host3 peut se faire comme suit:

 $ awk '/^[^#]/ && /client3/ { gsub(/filename[^;]+;/, ""); print; }' so host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; } 

Spécification d’une image personnalisée (pxecustom.0):

 $ awk '/^[^#]/ && /client3/ { gsub(/filename[^;]+;/, "filename \"pxecustom.0\";"); print; }' so host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; filename "pxecustom.0"; }