Journal taptempo.awk : une approche plus unix ?

Posté par . Licence CC by-sa
Tags :
44
5
mar.
2018

Salut !

Ce journal s'inscrit dans la série des journaux traitant de portages de taptempo.

Pour bien commencer la semaine, je voulais proposer une alternative à taptempo sous forme de filtre. Ma version d'un compteur de tempo se base sur l'outil xev et ne possède aucune option. Il est plus rapide de modifier directement le script que d'en modifier.

Pour moi c'est l'occasion de présenter awk, un langage de script relativement peu connu/utilisé alors qu'il est très intéressant. Il fait, pour moi parti de mon armement de base en shell avec bash/zsh, grep, sed et perl.

Awk ?

awk est un outil en ligne de commande qui permet de parser du texte. Il représente le texte comme des lignes et des colonnes. Le langage est particulièrement fait pour filtrer et découper les lignes selon les critères que l'on souhaite. Par défaut les retours à la ligne découpent les lignes et les champs (les colonnes) sont découpés sur les espaces (les champs vides sont ignorés).

Le code est organisé en bloc de code qui ont une condition devant eux. La condition peut être une expression régulière sur la ligne en cours de traitement, une condition "quelconque" (est-ce que la variable foo est égale à 3 ?), BEGIN ou END. Ces 2 dernier représentent le début et la fin du programme.

On accède à la ligne via des variables :

  • $0 représente toute la ligne
  • $1, $2,… le premier champ, le deuxième champ,…

Attention les variables ne sont pas des copies de la ligne, mais une référence. Ça signifie que si j'affecte une valeur à $1 alors $0 en sera affecté. C'est plutôt pratique.

Astuces

A fin d'être shell ready, il y a différentes choses qui sont disponibles de bases pour réduire la taille des scripts.

  • Les conditions sont optionnelles, un bloc sans condition sera toujours évalué ;
  • Les blocs sont optionnels : l’absence de bloc est équivalent à {print $0} ;
  • Beaucoup de fonctions de traitements de chaines prennent par défaut $0. Du coup print est équivalent à print $0.

Dans la vie quotidienne

Dans la vie, j'utilise awk en remplacement de grep + sed ou en remplacement de cut (j'aime pas bien cut). Par exemple :

grep foo fichier | cut -f3

sera remplacé par :

awk '/foo/{print $3}' fichier

Et ton taptempo, alors ?

C'est pas vraiment un taptempo, c'est une alternative… Mais le voila :

#!/usr/bin/awk -f

BEGIN {
    size=15.;
    c=1;
}
/KeyPress event/{
    k=1;
}
k == 1 && /^    root /{
    sub(",", "", $6);
    t[1+(c % size)]=$6;
    if(c > size){
        start=1+c%size;
        end=1+(c-size+1)%size;
        interval=t[start]-t[end];
        if(interval > 0) {
            print 60000/(interval/size), "BPM";
        }
        else {
            print "Too speed";
        }
    }
    c++;
    k=0;
}

et il s'utilise via la ligne :

xev -event keyboard | ./taptempo.awk

J'utilise le temps mesuré par xev plutôt que la fonction systime() d'awk pour avoir une précision à la miliseconde plutôt qu'à la seconde.

Bonne semaine à tous !

Note: j'utilise xev, je ne connais que lui pour faire ça, mais peut être qu'il y en a d'autres plus pratiques

  • # xev

    Posté par (page perso) . Évalué à 4 (+3/-0).

    Bravo pour cette adaptation, je trouve ton approche très créative !

    Je viens de tester sur une Debian Stretch, et rien ne s'affiche. Peut-être que la sortie de xev n'est pas celle attendue ?
    Lorsqu'une touche est frappée, xev -event keyboard affiche ceci :

    KeyPress event, serial 28, synthetic NO, window 0x1000001,
        root 0x281, subw 0x0, time 232765, (-332,681), root:(632,701),
        state 0x0, keycode 26 (keysym 0x65, e), same_screen YES,
        XLookupString gives 1 bytes: (65) "e"
        XmbLookupString gives 1 bytes: (65) "e"
        XFilterEvent returns: False
    
    • [^] # Re: xev

      Posté par . Évalué à 2 (+1/-0).

      Tu as bien sélectionné la fenêtre que xev crée d'appuyer sur des touches ?

    • [^] # Re: xev

      Posté par (page perso) . Évalué à 3 (+2/-0).

      En fait le calcul du tempo se déclenche après 15 appuis successifs (variable size=15. au début), je n'avais pas été assez patient…

      Donc tout fonctionne bien !

      • [^] # Re: xev

        Posté par . Évalué à 2 (+1/-0).

        Arf c'était une valeur de test pour moi j'ai oublié de le changer avant de publier. Désolé.

    • [^] # Re: xev

      Posté par . Évalué à 1 (+0/-0). Dernière modification le 05/03/18 à 15:33.

      Des BPM s'affichent bien, il y a une fenêtre "Event Tester" qui doit apparaître, il faut taper sur le clavier avec le focus sur cette dernière.
      EDIT: BBQ :-)

  • # awk vs cut

    Posté par . Évalué à 0 (+1/-2).

    Ok, awk est très puissant.
    Par contre, si je souhaite parser des gros fichiers avec le minimum de ressources ou le plus rapidement possible, alors je préfère utiliser grep+cut pour sa sobriété et rapidité.

    • [^] # Re: awk vs cut

      Posté par . Évalué à 4 (+4/-1).

      Je pense que tu sous estimes les capacités de filtrage de awk, grep et cut sont très efficaces dans beaucoup de cas mais awk pour le filtrage complexe à de gros avantages.

    • [^] # Re: awk vs cut

      Posté par . Évalué à 4 (+3/-0).

      Hum… Je ne sais pas ce que tu entends par sobriété. awk s'inscrit dans la veine des grep/cut/sed. Lui donner des centaines de Mio de données ne lui fais pas particulièrement peur.

      D'un point de vue performance, selon les cas grep+cut peuvent être plus performance grâce pipelining (mais si ton pipeline as plus d'étapes que tu n'a de cœurs tu ne gagne rien). Sur des exécutions rapide mais fréquentes le fait d'utiliser un seul processus va par contre te faire gagner du temps. Enfin awk permet d'exprimer des choses autrement.

      Exprimer ce que j'ai fais en awk avec grep+cut ça donne :

      xev | grep -A1 "KeyPress event" | grep "    root" | cut -c31-36

      et ça ne gère pas un time dont la taille serait variable (il faudrait faire un cut par field puis retirer les dernier caractère).
      Avec grep+sed ça marche mieux :

      xev | sed -n '/KeyPress event/{n;s/    root.* time \([0-9]*\), .*/\1/p}'

      Mais bon on perds en lisibilité. La majeur partie du temps je me sert d'awk pour extraire des données depuis un ps, lsof, etc et je trouve awk beaucoup plus pratique pour ça.

      Je n'ai personnellement jamais mis en défaut gred, sed, cut ou awk (et pourtant j'aimerais bien pour avoir l'occasion de jouer avec perl).

      • [^] # Outils de base et awk vs Perl

        Posté par . Évalué à 6 (+4/-0).

        Je n'ai personnellement jamais mis en défaut gred, sed, cut ou awk (et pourtant j'aimerais bien pour avoir l'occasion de jouer avec perl).

        Mais quand on commence à vouloir croiser les données issues de plusieurs fichiers (ou sorties de processus) différents, les outils de base d’Unix avouent leurs limites : le principe du pipe, ça va bien tant qu’on n’a qu’une source… L’avantage de Perl par rapport à des outils Unix « pipés », c’est qu’on reste dans son script, on ne perd pas ses variables à chaque étape.

        Bon, awk tient quand même la route plus longtemps que les autres outils de base.
        Malgré cela, je ne l’utilise quasiment plus, parce que tout ce qu’on peut faire en awk, on peut le faire en Perl en à peine plus long, et même quasiment à l’identique si on veut*, mais que si jamais on veut faire d’autres trucs ensuite, avec Perl, on a toute la puissance du langage, de sa bibliothèque standard et de son dépôt (dont Time::HiRes, utilisé par TapTempo.pl)…

        * Par exemple, on peut transcrire le script awk de ce journal simplement en :

        #!/usr/bin/perl -nal
        $, = ' ';
        
        BEGIN {
            $size=15;
            $c=1;
        }
        if (/KeyPress event/){
            $k=1;
        }
        if ($k && /^    root /){
            $F[5] =~ s/,//;
            $t[1+($c % $size)]=$F[5];
            if($c > $size){
                $start=1+$c%$size;
                $end=1+($c-$size+1)%$size;
                $interval=$t[$start]-$t[$end];
                if($interval > 0) {
                    print 60000/($interval/$size), "BPM";
                }
                else {
                    print "Too fast";
                }
            }
            $c++;
            $k=0;
        }

        Je vous invite à faire une comparaison avec la version awk du journal à l’aide de meld ou tkdiff ; ce n’est pas un hasard que ce soit si similaire : les premières versions de Perl visaient justement à remplacer le shell et les outils du système, notamment awk, en offrant des possibilités plus vastes.

        Ce qui permet d’avoir une structure aussi semblable, ce sont les options :
        -n permet de boucler sur les lignes des fichiers passés en arguments ou de l’entrée standard,
        -a active leur découpage automatique dans le tableau @F,
        -l retire les fins de lignes en entrée et en remet en sortie,
        $, = ' '; ajoute un espace comme séparateur de champ en sortie (pour faire comme awk).
        Et aussi le fait que les lignes de l’entrée sont affectée à une variable implicite ($_) à laquelle sont appliquées par défaut les expressions régulières.

        Bon, si je ne cherchais pas à coller à la version originale en awk, j’opterais pour parser explicitement l’entrée plutôt que de traîner un BEGIN, séparer les champs explicitement (pas la peine de le faire pour toutes les lignes, alors que seules certaines nous intéressent), je passerais en mode strict (ce qui implique de déclarer les variables, mais permet qu’on soit averti d’une faute de frappe dans un nom de variable)… Sans changer l’algorithme, ça ressemblerait à ça (j’ai commenté les spécificités de Perl dans le code) :

        #!/usr/bin/perl -w
        # -w pour les avertissements ("warnings")
        use strict;
        # Interdit certaines constructions pouvant correspondre à une erreur (variables
        # non déclarées, etc.).
        
        my $size = 15;
        my $c    = 1;
        my ($k, @t);
        # Si on ne précise rien, une variable scalaire est initialisée à undef et un
        # tableau à tableau vide.
        
        while (<>) {
        # Cette construction parcoure les lignes de l’entrée en affectant la ligne
        # courante à la variable implicite $_
        
            /^KeyPress event/ and $k = 1;
            # Sans précision, une expression régulière s’applique à la variable
            # implicite (c’est pour ça qu’on l’appelle variable implicite…).
        
            if ($k && /^    root /) {
                my @f = split;
                # Sans argument, split s’applique aussi sur $_.
        
                $f[5] =~ s/,$//;
                $t[1 + ($c % $size)] = $f[5];
        
                if ($c > $size) {
                    my $start = 1 + $c % $size;
                    my $end = 1 + ($c - $size + 1) % $size;
                    my $interval = $t[$start] - $t[$end];
        
                    if ($interval > 0) {
                        print 60000 / ($interval / $size), " BPM\n";
                    } else {
                        print "Too fast\n";
                    }
                }
        
                ++$c;
                $k = 0;
            }
        
        }

        Pourquoi j’utilise quelquefois (rarement) awk quand même ?
        Parce que sa force est d’être installé en standard. Ça m’arrive de vouloir un script qui tourne tout de suite sur n’importe quel système (de type Unix), et que sur certaines distributions indigentes (ou systèmes indigents), Perl n’est pas installé par défaut (ou présent dans la version live).

        Après, quand on veut un script vraiment passe partout (majorité des distributions Linux, systèmes *BSD, autres Unix), on ne peut pas compter sur les fonctionnalités avancées de GNU awk (pas communes à toutes les versions d’awk) et on peut oublier aussi bash, les *BSD ne l’ont pas forcément d’origine. Du coup, il faut se rabattre sur /bin/sh, mais le dénominateur commun entre bash et le shell des *BSD est déjà pas mal limité. Mais si on cible aussi OpenIndiana, c’est le drame (/bin/sh y correspond à un shell historique notoirement bogué et très limité, conservé pour garantir de ne pas mettre en défaut de vieux scripts que les utilisateurs ont pu développer).

        On peut craindre que les mainteneurs d’une distribution aient l’idée de supprimer awk parce que c’est vieux et qu’ils fournissent bien mieux… sauf que leur truc bien mieux ne sera pas d’origine partout.

        Alors, si on n’a plus que le shell, il permet toutefois de faire des opérations sur les chaînes, mais avec une syntaxe bien plus absconse que Perl, awk ou sed, et les plus pratiques ne sont pas supportées par tous les shells (voire par très peu).

        Théorie du pot-au-feu : « Tout milieu où existe une notion de hauteur (notamment les milieux économique, politique, professionnels) se comporte comme un pot-au-feu : les mauvaises graisses remontent. »

        • [^] # Re: Outils de base et awk vs Perl

          Posté par . Évalué à 3 (+2/-0).

          Je suis parfaitement d'accord avec toi ! C'est uniquement parce que je veux pousser mes outils dans leur limite avant de passer à l'artillerie au dessus que je ne remplace pas mon awk par perl. J'avais découvert toutes ces possibilités avec l'article : http://articles.mongueurs.net/magazines/perles/perles-38.html qui permet de se rendre compte de comment il fonctionne de manière assez ludique :)

          • [^] # Re: Outils de base et awk vs Perl

            Posté par . Évalué à 3 (+1/-0).

            C'est uniquement parce que je veux pousser mes outils dans leur limite avant de passer à l'artillerie au dessus que je ne remplace pas mon awk par perl.

            J’ai des collègues qui font du bash tant que ce n’est pas trop compliqué.
            Puis quand ça commence à être la galère avec bash (on peut aller assez loin avec bash — surtout en utilisant ses spécificités, mais c’est au détriment de la portabilité —, mais à un certain stade, même s’il peut encore faire le job, ce n’est plus l’outil le plus performant en temps de développement), ils refont en Python.

            Pour ma part, j’utilise Perl dès le départ, et quand ça devient plus complexe, j’étends le script sans problème. Souvent, le besoin initial semble simple, puis on s’aperçoit que ce serait bien que ça fasse aussi ci ou ça, et de fil en aiguille, on arrive à quelque chose de bien plus complexe que ce qu’on prévoyait.

            À la limite, la plus grosse adaptation avec Perl, c’est quand on passe d’un uniligne avec des options à un « vrai » script, mais man perlrun ne se contente pas de décrire les options de perl, il indique aussi leur équivalent en code Perl.

            Théorie du pot-au-feu : « Tout milieu où existe une notion de hauteur (notamment les milieux économique, politique, professionnels) se comporte comme un pot-au-feu : les mauvaises graisses remontent. »

            • [^] # Re: Outils de base et awk vs Perl

              Posté par . Évalué à 2 (+1/-0).

              En fait, mon usage n'est pas le même que le tiens.

              Je n'ai pas de problème de portabilité. Il est très rare que j'écrive un script qui sort de ma machine. L'énorme majorité du temps c'est pour mon usage personnel. Quand j'en écris un pour un serveur au boulot j'utilise le bourn shell ou python parce que c'est ce qui est le plus maitrisé par mes collègues. Quand c'est sur ma machine j'utilise zsh la majorité du temps.

              J'utilise du coup le même langage en interactif (zsh est mon login shell) et pour mes scripts. La genèse de mes scripts est souvent intéractif.

    • [^] # Re: awk vs cut

      Posté par . Évalué à 4 (+1/-0).

      Moi aussi quand je peux j’utilise cut… mais awk a un sacré avantage sur cut… c’est quand dans ta sortie tu as des champs séparés par un nombre variable d’espaces (et ça arrive souvent). Avec awk tu fais {print $3,$4} tu as les troisième et quatrième champ facilement alors qu’avec un cut -d' ' -f3,4 ça ne fonctionnera pas. Il ne me semble pas que cut ait une option pour supporter un nombre variable de séparateurs.

      De plus, tu peux filtrer et « couper » avec un seul appel à awk, alors que si tu appelles grep+cut tu vas avoir besoin d’un pipe ou d’une redirection et tu appelles deux programmes différents. Si ton « filtrage+coupage » est au milieu d’une boucle, exécuté un nombre important de fois, ça fera une différence sur le temps d’exécution de ton script au final.

      • [^] # Re: awk vs cut

        Posté par . Évalué à 0 (+0/-1).

        alors qu’avec un cut -d' ' -f3,4 ça ne fonctionnera pas.

        Là c'est toi qui sous estime cut :

        grid@srvorion01:+ASM:tmp> cut -d\  -f3,4<<<"field1 field2 field3 field4 field5 field6"
        field3 field4
        grid@srvorion01:+ASM:tmp> awk '{ print $3,$4 }'<<<"field1 field2 field3 field4 field5 field6"
        field3 field4
        

        Si tu voulais les champs de 3 à 5 : -f3-5

        • [^] # Re: awk vs cut

          Posté par . Évalué à 3 (+2/-0). Dernière modification le 06/03/18 à 16:12.

          Tu n'a pas compris en ayant 2 espace entre le champ 3 et 4 :

          % cut -d\  -f3,4<<<"field1 field2 field3  field4 field5 field6"
          field3 
          % awk '{ print $3,$4 }'<<<"field1 field2 field3  field4 field5 field6"
          field3 field4
          • [^] # Re: awk vs cut

            Posté par . Évalué à 1 (+0/-0).

            Désolé je n'avais effectivement pas lu "nombre variable d'espaces"

            • [^] # Re: awk vs cut

              Posté par . Évalué à 3 (+0/-0).

              J’ai failli le mettre en gras… Si j’avais su j’aurais dû :)

              • [^] # Re: awk vs cut

                Posté par . Évalué à 1 (+0/-0).

                Oui, surtout que je ne comprenais tellement pas ce que tu disais que je t'ai relu 3 ou 4 fois avant de faire un test. J'ai fait le test et je me suis dit que ça faisait trop longtemps qu'il n'avait plus utilisé cut ;)

  • # Un helloworld est né, il s'appelle taptempo ;-)

    Posté par (page perso) . Évalué à 10 (+10/-0).

    À force, il va falloir faire une depêche de tous ces journaux, avec des stats et tout le toutim, non?

    ⚓ À g'Auch TOUTE! http://afdgauch.online.fr

Envoyer un commentaire

Suivre le flux des commentaires

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