Forum Programmation.shell sed, c'est dien

Posté par  .
Étiquettes : aucune
2
9
juil.
2009
Bonjour :)

J'ai la joie de me frotter à sed. Chaque fois j'apprends une astuce. Ca fait beaucoup d'astuces je trouve :-)

J'ai ce genre de texte à traiter:
Unit UnitType Status %Cmpl Stripe Size(GB) Cache AVerify IgnECC
------------------------------------------------------------------------------
u0 RAID-1 OK - - 698.637 ON - -

Port Status Unit Size Blocks Serial
---------------------------------------------------------------
p0 OK u0 698.63 GB 1465149168 GTC200P8GXB012
p1 OK u0 698.63 GB 1465149168 GTC200P8GXB5G4

Je sors ça avec la commande:
tw_cli /$(tw_cli show | grep '^c[0-9] ' | cut --characters=1-2) show

C'est pour surveiller du RAID matériel 3ware.

Ce qui m'intéresse, c'est d'extraire les lignes ne contenant pas 'OK'.

J'ai donc deux groupes de lignes qui suivent '------'. La fin de ces groupes est une ligne vide.

Je peux résoudre mon problème à coups de grep et de cut, mais je veux apprendre à utiliser sed de meilleure manière. A moins que awk soit plus indiqué dans ce cas précis ?

Actuellement j'utilise ça:
echo '
Unit UnitType Status %Cmpl Stripe Size(GB) Cache AVerify IgnECC
------------------------------------------------------------------------------
u0 RAID-1 OK - - 698.637 ON - -

Port Status Unit Size Blocks Serial
---------------------------------------------------------------
p0 OK u0 698.63 GB 1465149168 GTF200P8GZB0XF
p1 ERROR u0 698.63 GB 1465149168 GTF200P8GZB5LF

' | grep '^[pu][0-9]' | grep --invert-match 'OK'

Ca fonctionne bien, mais ce n'est pas idéal.

Dans mon idée, il faut que sed affiche les lignes souhaitées (uniquement le champ voulu de chaque ligne), puis n'affiche finalement que celles ne contenant pas OK.
Au final, j'ai un affichage vierge si tout est bon, sinon j'ai au moins une ligne.

Je sais faire un traitement sur des groupes de lignes avec:
sed -n /^---/,/^$/ mon_traitement_ici

Par contre je ne sais pas comment faire pour afficher uniquement le troisième champ si pas OK, puis le second champ si pas OK. Je cale :)
  • # awk

    Posté par  . Évalué à 6.

    Je pense qu'awk est plus intéressant dans ce cas, après tout sa spécialité c'est le traitement ligne par ligne.

    J'obtiens le même résultat avec la commande suivante :


    echo '
    Unit UnitType Status %Cmpl Stripe Size(GB) Cache AVerify IgnECC
    ------------------------------------------------------------------------------
    u0 RAID-1 OK - - 698.637 ON - -

    Port Status Unit Size Blocks Serial
    ---------------------------------------------------------------
    p0 OK u0 698.63 GB 1465149168 GTF200P8GZB0XF
    p1 ERROR u0 698.63 GB 1465149168 GTF200P8GZB5LF

    ' | awk '{if(!match($0,'/OK/')){print $0}}'


    Si tu es sur que les test se feront toujours sur les lignes commençant par u ou p alors tu peux mettre :

    awk '/^u|^p/{if(!match($0,'/OK/')){print $0}}'

    Il me semble que c'est plus performant, si tu as un grand nombre de lignes bien sur.

    "Aucun de nous ne sait ce que nous savons tous, ensemble", attribué à Laozi.

    • [^] # Re: awk

      Posté par  . Évalué à 3.

      Ton code lance une fois awk au lieu de deux fois grep, pas mal.

      Je me demande si on peut faire la même chose avec un seul grep. Je m'attendait à ce que grep '^[pu][0-9].*(OK){0,0}' fonctionne... mais non :-)

      Comment grep peut donner les lignes qui contient x mais pas y ?
      Avec deux grep, facile: grep x | grep --invert-match y
      Mais avec un seul ?
      • [^] # Re: awk

        Posté par  . Évalué à 3.

        Je crois pas qu'on puisse le faire avec grep. Avec sed peut-être mais je suis pas un expert.

        L'avantage d'utiliser awk c'est qu'on peux travailler sur les lignes à retourner. Dans cet exemple, on pourrait retourner le nombre de ligne de résultat, ou calculer la somme des champs Size des lignes d'erreur, ou simplement extraire un seul champ de la ligne de résultat (récupérer que le Serial pour appeler automatiquement un script. qui fournirait plus d'infos sur l'erreur).

        J'ai pas mal joué avec awk sur des gros fichiers de logs, c'est un outil vraiment puissant.

        "Aucun de nous ne sait ce que nous savons tous, ensemble", attribué à Laozi.

        • [^] # Re: awk

          Posté par  (site web personnel) . Évalué à 7.

          • Avec un seul grep, on peut :
             grep -vE '(^[^pu]|OK)'
            
            en utilisant la négation.
          • Sed :
             sed -ne '/^[pu]/{/OK/!p}'
            
            ou encore :
            sed -e '/^[pu]/!d;/OK/d'
            
          • Awk peut être plus concis :
             awk '/^[pu]/ && ! /OK/'
            
            (l'action par défaut est print $0).
          • [^] # Re: awk

            Posté par  . Évalué à 4.

            Bien vu pour le grep !

            Il faut modifier un peu car sinon on a les lignes vides:
            grep -vE '(^[^pu]|OK|^$)'

            Cependant je ne trouve pas comment on pourrait faire si on souhaite que le début de la ligne ne soit pas un seul caractères mais plusieurs. Dans l'exemple de gremous c'est [pu][0-9].
            J'ai essayé avec [^pu][^0-9] mais ça ne donne rien de plus.

            Pour le sed c'est facile:
            sed -n '/^[pu][0-9]/{/OK/!p}'
            • [^] # Re: awk

              Posté par  (site web personnel) . Évalué à 1.

              On peut utiliser
               grep -vE '(^[^pu]|^[pu][^0-9]|OK|^$)'
              
              mais si on allonge l'expression ça devient vite horrible... Pour une expression logique, c'est Awk le plus clair et lisible.
              • [^] # La soluce

                Posté par  . Évalué à 2.

                Voilà ma solution complète avec sed.
                sed -nr --expression='/^u[0-9]/{/ {4}[^ ]+ +OK +/!p}' --expression='/^p[0-9]/{/ {5}OK +/!p}'

                Ca affiche les lignes qui commencent par p0 ou u0 par exemple, mais qui ne répondent pas aux critères qui suivent. Donc le moindre pet de travers, zou une alerte.

                Il me reste un problème mineur: je suis obligé d'indiquer le nombre d'espaces que je dois trouver après p0 ou u0. Si je mets juste ' +' ça ne génère pas de ligne si elle commence par 'u0000' par exemple.
                Comment faire pour "ancrer" le début du motif juste à la suite de /^u[0-9]/ ?

                Je vais me frotter à awk maintenant :)

                Merci pour votre aide !
                • [^] # Re: La soluce

                  Posté par  . Évalué à 1.

                  sed ne comprends pas le +, il faut donc expliciter. Par exemple: [a-z]+ devient [a-z][a-z]*
                  • [^] # Re: La soluce

                    Posté par  . Évalué à 1.

                    Ton herbe ne vient pas de ta pelouse, c'est certain :-)
                  • [^] # Re: La soluce

                    Posté par  . Évalué à 3.

                    Ben '+' == \{1,\}, '?' == \{0,1\}.

                    On a donc plutôt /[a-z]\{1,\}/ pour faire l'équivalent de /[a-z]+/ .

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.