Bash : compter les heures, ajouter un 0 devant une heure inférieure à 10
J’ai été confronté à un problème simple à la base (pour un adminsys) : compter le nombre de requêtes sur un site, heure par heure, à partir d’un fichier access_log Apache. Finalement le problème était un peu plus tricky, et ça demande un peu plus d’explications qui ne tiennent pas dans une astuce diverse.
Le log apache est facile à lire via bash, il contient la date et l’heure dans un format que grep n’aura aucun mal à identifier. Je shoot donc un one-liner rapide :
1 |
for i in {0..23}; do echo -ne "${i}h : $(grep "2017\:$i" access.log |wc -l)\n"; done |
Simple, rapide, efficace, le quotidien du sysadmin quoi. Enfin efficace… Le résultat est sur le coup surprenant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
0h : 177026 1h : 294004 2h : 84661 3h : 0 4h : 0 5h : 0 6h : 0 7h : 0 8h : 0 9h : 0 10h : 24228 11h : 29431 12h : 28586 13h : 34634 14h : 34067 15h : 32441 16h : 28779 17h : 30497 18h : 27450 19h : 23891 20h : 21230 21h : 19748 22h : 21587 23h : 22096 |
Damned. J’imagine mal un site public sous WordPress capable de ne pas subir la moindre requête pendant plusieurs heures, entre monitoring, intervention client ou robots d’indexation. J’ai donc fait une connerie. Une connerie vite identifiée vu le pépin : l’heure est notée sur deux chiffres dans les logs, hors il n’y a que 0,1,2 qui peuvent matcher dans ce cas, ce qui explique les trous de 3 à 9.
Certains furieux trouveraient certainement une regex ultra cradingue à exploiter à la place (et faut pas hésiter à partager en commentaires), j’ai fait un peu différent (c’est surtout pour ça que je voulais en parler), à savoir passer deux ensembles de nombres à for :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
for i in 0{1..9} {10..23}; do echo -ne "${i}h : $(grep "2017\:$i" starafrica-20170628.log |wc -l)\n"; done 01h : 24983 02h : 19756 03h : 14581 04h : 11723 05h : 10904 06h : 11458 07h : 15557 08h : 17999 09h : 21149 10h : 24228 11h : 29431 12h : 28586 13h : 34634 14h : 34067 15h : 32441 16h : 28779 17h : 30497 18h : 27450 19h : 23891 20h : 21230 21h : 19748 22h : 21587 23h : 22096 |
C’est beaucoup mieux.
Oui, on peut indiquer deux plages de valeurs sans avoir à faire deux boucles. Une bizarrerie découverte au hasard d’une recherche de solution à mon petit souci. En fait, dans la pratique les deux ensembles sont décomposés par Bash pour ne constituer qu’une seule liste, ce qui donne l’équivalent de la syntaxe suivante, bien plus longue et lente à écrire :
1 |
for i in "00" "01" "02" "03" "04" "05" ... "23"; do ... |
Ce langage ne cesse de m’étonner, j’ai l’impression qu’on pourrait y passer une vie sans en avoir fait le tour.
Bon ça m’empêchera pas de continuer à m’intéresser à PHP et Python 😛
Et l’heure 0? :0
Y’a encore mieux, en fait.
Pour faire la liste tu peux simplement utiliser {00..23}, pour les valeurs de 0 à 9 bash va automatiquement insérer un 0 au début 🙂
Bonjour à toi,
Tu peux aussi utiliser la commande seq -w 23 (ou seq -w 0 23)
Saeroshi.
En plus compliqué : for i in {0..23}; do echo -ne "${i}h : $(grep "2017:$(printf '%02d' $i):" access.log |wc -l)n"; done Dans ce cas, c’est pas très utile, mais dans d’autres, ça peut, par exemple si on veut jouer avec de l’hexadécimal : for i in {0..255}; do printf '%02x ' $i; done' Dans tous les cas, le grep est peu précis et il pourrait matcher le user agent d'un troll… Aussi, logrotate (ou équivalent) peut passer et fausser les résultats. Je ferais plutôt :mawk ‘{gsub(« : », » « , $5); count[substr($5, 2, 14) »h: »]++} END {for (date in count) print date, count[date]}’… Lire la suite »
Petite coquille à la fin sur la ligne de script shell : ‘for in in « 00 » « 01 » « 02 » « 03 » « 04 » « 05 » … « 23 »; do …’
Je crois pas que bash tolère les doubles in, ça doit juste être un i travesti en in.
Article sympathique sinon. 🙂