Ziirish's Home :: Blog

Ziirish's Pub

 
 

Quand j'étais encore à l'école, on me disait qu'un bon admin est avant tout un bon fainéant. Je ne sais pas si la réciproque est vraie, mais je sais que je suis un sacré fainéant.

Du coup, chaque fois qu'on me demande de faire quelque chose que j'ai déjà fait par le passé, et qui nécessité pas mal d'opérations de ma part, j'essaie de factoriser. Quand c'est sur du click'o'lol, j'ai un peu de mal, mais en général, j'évite le plus possible ce genre de choses. La factorisation est donc intéressante quand il s'agit de Shell.

Dernièrement, je me suis constitué un petit framework ayant pour rôle de gérer pour moi les logs, et d'être capable de continuer de fonctionner, même si je perds la connexion.

Je sais, je pourrais utiliser Screen, mais si j'avais vraiment mon mot à dire, j'aurais pas non plus de saleOS sur ma machine pro, etc.

Tout ça pour dire qu'en tant que junior, je fais mon boulot du mieux que je peux, avec les outils que j'ai à ma disposition, même si j'espère un jour avoir l'opportunité de travailler dans un environnement 100% propre.

Fin de la parenthèse, revenons à nos moutons. J'ai donc à ma disposition un framework capable de gérer ses logs et de fonctionner "tout seul". Comme je m'en sers régulièrement, je me suis dit que ça pourrait éventuellement servir à d'autres ;)

framework.sh


# initialisation des logs
init() {
    [ $MY_LOG -eq 1 ] && {
        [ ! -d "$MY_LOG_DIR" ] && mkdir -p $MY_LOG_DIR
        MY_LOG_FILE=$MY_LOG_DIR/$MY_LOG_FILE
        MY_LOG_IMP=$MY_LOG_DIR/$MY_LOG_IMP
        echo "">$MY_LOG_FILE
    }
    end
}

# Recupere le path de la commande passee en argument
# @param: $1, nom 'simple' de la commande a generer
get_cmd() {
    cmd=$(tr [a-z] [A-Z] <<<$1)
    cmd='CMD_'$cmd
    echo ${!cmd}
}

# Execute une commande et attend qu elle termine
# @param: $@, commande a executer
run() {
    execute $@ &
    waiting $(basename $(cut -d' ' -f1 <<<$1)) $! $@
}

# Attend la fin d une commande
# @param: $1, nom de la commande a attendre
# @param: $2, pid de la commande a attendre
# @param: $@, commande complete
waiting() {
    name=$1
    shift
    pid=$1
    shift
    [ $MY_LOADING -eq 1 ] && {
        echo -ne "Waiting for $name... \t   "
        animation_state=1
        while :
        do
            ps $pid >/dev/null 2>&1
            ret=$?
            [ $ret -ne 0 -o $MY_DEBUG -eq 1 ] && break
            echo -e -n "\b\b\b"
            case $animation_state in
            1)
                echo -n "["
                echo -n -e "\033[1m|\033[0m"
                echo -n "]"
                animation_state=2
            ;;
            2)
                echo -n "["
                echo -n -e "\033[1m/\033[0m"
                echo -n "]"
                animation_state=3
            ;;
            3)
                echo -n "["
                echo -n -e "\033[1m-\033[0m"
                echo -n "]"
                animation_state=4
            ;;
            4)
                echo -n "["
                echo -n -e "\033[1m"
                echo -n "\\"
                echo -n -e "\033[0m"
                echo -n "]"
                animation_state=1
            ;;
            esac
            sleep 1
        done
        echo -e -n "\b\b\b"
    }
    wait $pid >/dev/null 2>&1
    ret=$?
    [ $ret -eq 0 ] && {
        [ $MY_LOADING -eq 1 ] && echo "[OK]"
    } || {
        [ $MY_LOADING -eq 1 ] && echo "[KO]"
        error $ret $@
    }
}

# Execute une commande 'generique' passee en argument et traite les erreurs
# @param: $@, commande a executer
execute() {
    [ $MY_VERBOSE -eq 1 ] && echo -e $@
    [ $MY_LOG -eq 1 ] && echo -e $@ >>$MY_LOG_FILE
    [ $MY_DEBUG -eq 1 ] || { 
        eval "$(echo -e $@)" >>$MY_LOG_FILE 2>&1
    }
}

# Execute la commande passee en argument a partir de son nom 'simple'
# @param: $1, commande a 'construire'
# @param: $@, options (facultatives)
run_cmd() {
    cmd=$1
    shift
    run $(get_cmd $cmd) $@
}

# Traitement des erreurs
error() {
    code_err=$1
    shift
    [ $code_err -eq 0 ] && return
    echo "Error: failed to run '$@'. Command returned code error $code_err." 1>&2
    [ $MY_LOG -eq 1 ] && {
        echo "Read $MY_LOG_FILE for more details."
        echo -e "Error: failed to run '$@'. Command returned code error $code_err." >>$MY_LOG_FILE
    }
    end
    exit `expr $code_err + 50`
}

# Fin du programme
end() {
    rm=$(rm -vf $MY_TMP_PARFILE 2>/dev/null)
    ret=$?
    [ $ret -eq 0 -a $MY_VERBOSE -eq 1 ] && echo $rm
    [ $ret -eq 0 -a $MY_LOG -eq 1 ] && echo $rm >>$MY_LOG_FILE
}

# path to binary
for exe in $MY_REQUIRED_TOOLS
do
    tmp="CMD_"$(tr [a-z] [A-Z] <<<$exe)"="$(which $exe 2>/dev/null)
    eval $tmp
    [ -z "$(cut -d= -f2 <<<$tmp)" ] && {
        echo "Error: command '$exe' not found."
        exit 1
    }
done    

[ ! $MY_LOG -eq 1 ] && MY_LOG_FILE="/dev/null"

Toutes les fonctions sont commentées. Voici désormais un squelette de script utilisant le framework.

skel.sh


#!/bin/bash

####################################################
# 1. VARIABLES
####################################################

# options du script
MY_DEBUG=0
MY_VERBOSE=1
MY_SPECIF=1
MY_LOADING=1
MY_LOG=1
MY_LOG_IMP="imp_update.log"
MY_LOG_FILE="update.log"
MY_LOG_DIR="$HOME/update"

# variables globales
MY_ARGC=$#
MY_ME=$(basename $0)

MY_TMP_PARFILE="/tmp/imp_"$RANDOM".PARFILE"
MY_REQUIRED_TOOLS="ftp exp imp sqlplus"

####################################################
# 2. FONCTIONS UTILITAIRES
####################################################

. /opt/tools/framework.sh

# Permet d afficher la notice du script en cas de besoin

usage() {
    [ $MY_ARGC -lt 5 -a $MY_SPECIF -ne 1 ] && {
        echo "Usage:"
        echo -e "t$MY_ME <host> <user> <password> <path> <file>"
        exit 2
    }
    [ $MY_ARGC -gt 1 -a $MY_SPECIF -eq 1 ] && {
        echo "/!\\Warning/!\\"
        echo -n "The script is configured to be launched with 'hard-coded' values, but you specified "
        echo "values on the command-line."
        exit 3
    }
}

####################################################
# 3. INITIALISATION ET VERIFICATION DES OUTILS
####################################################

# On verifie que le script est correctement initialise
usage

init

####################################################
# 4. DEBUT DU TRAITEMENT
####################################################



# On supprime nos eventuels fichiers temporaires
end

exit 0

Il ne nous reste plus qu'à adapter la partie 4) en fonction de nos besoins. On jouera ensuite avec les différentes variables dont voici la signification :

    MY_DEBUG : Lorsque vous positionnez cette variable à 1, le script n'effectuera aucun traitement réel. Utile afin de vérifier que tous les paramètres ont été saisis correctement. MY_VERBOSE : Positionnée à 1, cette variable permettra d'afficher toutes les actions du script sur la sortie standard. MY_SPECIF : Selon que vous souhaitiez passer des options au script ou utiliser des valeurs codées en dur. MY_LOADING : Permet d'afficher un prompt d'attente pour les traitements un peu longs. MY_LOG : Si vous voulez un fichier de log ou non.

Voici un exemple de ce qu'on obtient pour les traitements suivants avec toutes les options à 1 sauf MY_DEBUG qui est à 0


run_cmd dd if=/dev/zero of=/dev/null bs=1M count=10K

run_cmd cat /etc/shadow

ziirish@pluto:/opt/tools$ ./skel.sh 

/bin/dd if=/dev/zero of=/dev/null bs=1M count=10K
Waiting for dd...    [OK]
/bin/cat /etc/shadow
Waiting for cat...   [KO]
Error: failed to run '/bin/cat /etc/shadow'. Command returned code error 1.
Read ./update/update.log for more details.

ziirish@pluto:/opt/tools$ cat update/update.sh
/bin/dd if=/dev/zero of=/dev/null bs=1M count=10K
10240+0 records in
10240+0 records out
10737418240 bytes (11 GB) copied, 8.72318 s, 1.2 GB/s
/bin/cat /etc/shadow
/bin/cat: /etc/shadow: Permission denied
Error: failed to run '/bin/cat /etc/shadow'. Command returned code error 1.

La commande dd étant un peu plus longue, un prompt d'attente s'est lancé :


ziirish@pluto:/opt/tools$ ./skel.sh

/bin/dd if=/dev/zero of=/dev/null bs=1M count=10K
Waiting for dd...    [-]

Enfin, la variable MY_REQUIRED_TOOLS doit contenir la liste des outils nécessaires au bon déroulement du script. C'est toujours utile de savoir avant de dropper la base que la commande imp n'est pas disponible ;)

Vous pouvez ensuite appeler ces outils avec la commande run_cmd . Ça a aussi l'avantage d'indiquer le path complet de la commande lancée.

Si vous désirez lancer "n'importe quelle" commande, vous pouvez toujours utiliser run qui ne fait lui aucune vérification.