[gull] Truc et astuces: bash, sed et eval, partie 2: variables dynamiques

Félix Hauri felix at f-hauri.ch
Wed Nov 16 00:52:09 CET 2011


Bonjour les hackers!

On en remet une couche?

On Sun, Nov 06, 2011 at 06:11:43PM +0100, Arnaud wrote:
>
> J'avais un peu utilisé eval pour utiliser des noms de variables
> dynamiques, mais ça m'avait posé quelques soucis en bash ...

En préambule, je dirais qu'il faut faire attention, en jouant avec les
    variables d'environnement, de ne pas modifier des variables utiles!

    set | less
...
    Pour supprimer des variables, deux solutions: ``unset'' ou ``exit'' (Ctrl-D):
    .1: unset sert à supprimer des variables du shell courant;
	$ set | less
	$ ds='/^  %[^%]* /{s/^ *%\([^ ]*\) .*$/DATE_\1=\"%\1\"/;::;s/\([^:%]\):/\1_/;t:;p}'
	$ eval $(date +"$(date --help | sed -ne "$ds")")
	$ set | less
        $ unset $(set | grep ^DATE_ | cut -d= -f1 )
	$ set | less

    .2: les variables ne peuvent pas être exportées vers le parent:
	$ set | less
	$ /bin/bash
	$ set | less
	$ ds='/^  %[^%]* /{s/^ *%\([^ ]*\) .*$/DATE_\1=\"%\1\"/;::;s/\([^:%]\):/\1_/;t:;p}'
	$ eval $(date +"$(date --help | sed -ne "$ds")")
	$ set | less
	$ exit
	$ set | less

Voici, par exemple comment je repartitionne une clef usb, sur laquelle est
   installé Debian-Live avec une 2ème partition ``live-rw''. Je supprime 
   la 2ème partition, agrandi la 1ère et créé 2 partition sur l'espace 
   restant:

   * !!! En root, à vos risques et périls, donc! !!! *

   D'abord quelque variables de base:
   # STICK=sdX      # (ma clef usb)
   # UBUNTU=/tmp/ubuntu/ubuntu-11.10-desktop-i386.iso
   ( téléchargé à l'aide du script: http://f-hauri.ch/vrac/get_ubuntu.sh ;-)

   La taille en Mo (Mibi) du CD ubuntu
   # UBUSIZE=$(stat -c 1+%s/2^20 $UBUNTU  | bc )

   Là, je définis trois variables (en Mibi égallement): TOTSTICK taille tot
   du stick, FIRST première partition et STICKNAME le nom hardware du stick:
   # eval $(
	parted -sm /dev/$STICK 'unit Mi' 'print' |
	    sed -ne '
  s/^1:[^:]*:\([0-9]*\)Mi.*$/FIRST=\1/p;
  s/^\/[^:]*:\([0-9]*\)Mi.*:msdos:\(.*\);$/TOTSTICK=\1 STICKNAME="\2"/p;
	    ')   

    La première partitions doit donc être redimensionnée à (+3%):
    # NEWFIRST=$(( ( FIRST+UBUSIZE)*34/33 ))
    Le seconde doit utiliser la moitié du reste et la troisième, le reste.
    # SECOND=$(( (TOTSTICK-NEWFIRST)/2 +NEWFIRST ))

    La commande suivant *SUPPRIME* la 2ème partition (ATTENTION aux
    données perdues en cas de mauvaise application de ce qui suit!)
    # parted -sm /dev/$STICK 'unit Mi' \
	"resize 1 0 $NEWFIRST" \
	"mkpart primary ext3 $NEWFIRST $SECOND" \
	"mkpart primary ext3 $SECOND $TOTSTICK" \
	print
    # sync
    # sleep .5

    Et on formatte les partitions 2 et 3 labellées resp. live-rw et casper-rw
    # for NUM_LAB in 2:live-rw 3:casper-rw ;do
    echo mkfs.ext3 -L ${NUM_LAB#*:} /dev/${STICK}${NUM_LAB%:*} 
    done

Maintenant, voici une ``véritable application'' utilisant des variables
    dynamiques (calcul statistique sur syslog, compte les occurences et
    affiche les premier et dernière apparitions, pour chaque daemon.):
    Tout d'abord, il nous faut une copie de /var/log/syslog:

    $ sudo cat /var/log/syslog >/tmp/syslog

    $ while read -a line ;do
	nam=${line[4]%[*}
	nam=${nam##*/}
	nam=$(echo ${nam%:*}|
	    tr A-Z\\000-@ a-z_)
	nam=${nam##_}
	nam=${nam%%_}
	eval "[ \"\$FRST_$nam\" ] || FRST_$nam=\"${line[@]:0:3}\""
	eval "LAST_$nam=\"${line[@]:0:3}\""
	eval CUNT_$nam=$((CUNT_$nam+1))
	eval "printf \"\r%-25s %-16s %-16s %5d\" $nam \"\$FRST_$nam\" \
		\"\$LAST_$nam\" \$CUNT_$nam"
	[ "$ALLVARS" == "${ALLVARS%$nam*}" ] && ALLVARS="$ALLVARS $nam"
	done < /tmp/syslog ;echo

    Bon, en shell, ce n'est pas instantanné!
    plus de 5 minutes pour un fichier de 35'ooo lignes...
    $ printf "%s\n" $ALLVARS | sort | column

    Et pour finir:
    $ for VNAME in $ALLVARS;do
	eval printf \"%-25s %-16s %-16s %5d\\n\" $VNAME \"\$FRST_$VNAME\" \
		\"\$LAST_$VNAME\" \$CUNT_$VNAME
	done | sort -nk8

    ou (sans ``-nk8'')
    $ ... | sort

(Nota: Ce petit exercice pour évaluer la possibilité de créer et nommer des variables
 à la volée... En shell, cette opération n'est pas rentable. Il est probable qu'en
 exploitant mieux les finesses de Bash, on améliore le score, mais dans l'absolu,
 ce genre de truc est plus vite pondu en perl et nettement plus rapide:
    $ perl </tmp/syslog -e ' my %logs;
	while (<>) {
	    /^(\S+\s+\S+\s+\S+)\s+\S+\s+(\S*\/|)([^:\/\[ ]+)[:\[ ].*/ && do {
		(my $nam=$3)=~y|A-Z|a-z|;
		$logs{$nam}->{"last"}=$1;
		$logs{$nam}->{"first"}=$1 if 1 > $logs{$nam}->{"count"}++
	    };
	};
	map { printf "%-25s %-16s %-16s %5d\n", $_, $logs{$_}->{"first"},
		$logs{$_}->{"last"},$logs{$_}->{"count"}
	    } sort { $logs{$a}->{"count"} <=> $logs{$b}->{"count"} } keys %logs'
 environ 0,2 sec à l'execution, sur le même fichier et le même poste. ;)

-- 
 Félix Hauri  -  <felix at f-hauri.ch>  -  http://www.f-hauri.ch


More information about the gull mailing list