Forum Programmation.shell stderr dans une variable

Posté par  (site web personnel) .
Étiquettes : aucune
0
30
jan.
2008
Salut, j'ai écris un script pour sauvegarder des données et je suis tombe face à un problème.

Par exemple :
TAR=`tar -czvf /home/MonHome.tar.gz /tmp/ 2>> $LOGDIR`

Lorsque je lance à la main le script, tar est très polie et il me dit :
tar: Retrait de « / » de tête des noms des membres


Si je vais voir dans le fichier log que j'ai défini dans la variable j'ai en vrac la ligne tar: Retrait de « / » de tête des noms des membres alors que pour le reste des messages (ceux que je gère par les codes erreurs $?) j'ai défini un affichage comme ça :
jan 30 15:52:ss hostname backup[PID]: Erreur bidule chose


Moi ce que je voudrais c'est pouvoir mettre dans une variable toute sortie généré par une commande pour avoir des logs comme ça :
jan 30 15:52:ss hostname backup[PID]: Erreur machin truc
jan 30 15:52:ss hostname[PID]: tar: Retrait de « / » de tête des noms des membres


Vous avez une idée ?
Merci d'avance.
Philippe.
  • # récupérer la sortie de tar

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


    SORTIE=`tar -czvf /home/MonHome.tar.gz /tmp/ 2>&1`


    Tu as dans $SORTIE ce que t'a renvoyé la commande tar.
    echo $SORTIE
  • # à l'envers ;-)

    Posté par  . Évalué à 3.

    Bonjour,

    Je verrais bien une solution "à l'envers" à ton problème :P
    Comme tu n'as pas forcément besoin immédiatement de la sortie de la commande tar pour tes logs, c'est celle-ci que j'enverrais dans un fichier temporaire.
    Après, il suffit de lire les messages stderr et de les formatter comme bon te semble.

    Voici ce que cela donnerait (à tester !):

    # Je crée un fichier temporaire
    sortie_de_tar=$(mktemp -q)
    if [ ${?} -ne 0 ]; then
    echo Pas de bol '!'
    fi

    # Je lance l'archivage (attention a l'ordre des redirections !)
    tar -czvf /home/MonHome.tar.gz /tmp/ 2>&1 >"${sortie_de_tar}" \
    | while read ligne_erreur; do
    # Ici, formattage de la ligne
    echo $(date) $(hostname) ${ligne_erreur} >> "${LOGDIR}"
    done

    # Après, soit:
    TAR=$(< "${sortie_de_tar}")
    # ou
    TAR=$(cat "${sortie_de_tar}")

    # Petit ménage
    rm "${sortie_de_tar}"

    # La suite ici...


    Qu'en penses tu ?

    Bon courage !

    Cdlt,
    • [^] # Re: à l'envers ;-)

      Posté par  . Évalué à 1.

      Je proposerais également la solution de Steve.

      Un des avantages étant que si le programme échoue au milieu de l'exécution (disque dur plein, coupure de courant, etc...), tu peux quand-même consulter les logs.
    • [^] # Re: à l'envers ;-)

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

      Salut, merci pour vos réponses. Steve j'essai d'adapter ton code à mon script. Je me pose la question dans le cas ou j'ai plusieurs message à mettre.

      Voila un bout de mon script :
      #!/bin/sh
      #
      # Inclusion du fichier de configuration
      . /etc/backupeur/backupeur.conf

      # Initialisation des variables
      ID_ERREUR=0

      # controle des parametres --------------
      no_parametre() {
      echo "Erreur : vous devez donner une action"
      echo "Syntaxe : backup (file|mysql|bande)"
      echo " - backup file --> lance une sauvegarde sur disques dur"
      echo " - backup mysql --> sauvegarde uniquement les bases de données"
      echo " - backup bande --> lance la copie sur une péripherie type dat, lto"
      exit 1
      }

      # Définition des fonctions -------------
      # Mise à jour de la date pour les logs
      date_log () {
      DATE_LOG=`$BIN/date +%b" "%d" "%T`
      }

      # Creation de la variable pour la date
      # des fichiers sauvegardes.
      date_file () {
      DATE_FILE=`$BIN/date +%Y%m%d`
      }

      # Creation d'un fichier lock pour éviter de lancer la commande
      # de sauvegarde depuis un autre serveur
      create_lock_file() {
      LOCK_FILE=`$BIN/touch $BACKUP_DEST/$HOSTNAME.lock`
      }

      # Suppression du fichier lock
      delete_lock_file () {
      DELETE_LOCK_FILE=`$BIN/rm -f $BACKUP_DEST/$HOSTNAME.lock`
      if [ "$?" -ne 0 ]
      then
      MESSAGE="Suppression du fichier $1 impossible"
      erreur "$MESSAGE"
      fi
      }

      # Controle si un fichier lock pour empecher le
      # lancement de plusieurs sauvegarde en même temps
      # a partir d'un meme host
      # $1 est le nom du fichier lock pour la sauvegarde
      # en cours
      check_lock_file() {
      NBLOCK=`find $BACKUP_DEST -name $1 | wc -l`
      if [ $NBLOCK -gt 0 ]
      then
      MESSAGE="Impossible de lancer la sauvegarde, elle est déjà en cours"
      erreur "$MESSAGE"
      exit 1
      fi
      }

      # Suppression du fichier antérieur au
      # temps de retention
      # $1 correspond au prefix du nom du fichier
      old_file() {
      # Parcours du repertoire de destination
      # avec le filtre sur le nom du fichier
      for FILE in $BACKUP_DEST/*$1*
      do
      # Recupere le timestamp du fichier
      STAT=`stat -c '%Y' $FILE`

      # date du jour
      DATE=`date +%Y%m%d`

      # Convertion en timestamp
      TIMESTAMP_DAY=`date -d $DATE '+%s'`

      # calcul de l'écart en timestamp
      # par rapport au temps de retention
      DELTA=`$BINDIR/expr $RETENTION \* 86400`

      TIMESTAMP_MINI=`$BINDIR/expr $TIMESTAMP_DAY - $DELTA`

      if [ $STAT -lt $TIMESTAMP_MINI ]
      then
      RM=`$BIN/rm -f $FILE`
      if [ "$?" -no 0 ]
      then
      echo "Suppression du fichier $FILE impossible" > ${LOGFILE}
      fi
      fi
      MESSAGE="Suppression du fichier $1-$OLD_DATE.tar.gz"
      log "$MESSAGE"
      done
      }

      backup_mysql() {
      # Creation du nom du fichier lock
      NAME_LOCK_FILE="$HOSTNAME.lock"

      # Controle si une sauvegarde est déjà en cours
      check_lock_file "$NAME_LOCK_FILE"

      # Création du fichier lock
      create_lock_file "$NAME_LOCK_FILE"

      # Cree la date inclue dans le nom de l'archive
      date_file

      # Compte le nombre de ligne dans le tableau
      NBBASE=`echo ${#BACKUP_MYSQL[*]}`

      I=0
      while [ $I -lt $NBBASE ]
      do
      FILENAME=`echo ${BACKUP_MYSQL[$I]}`
      DATANAME=`echo ${BACKUP_MYSQL[$I]}`

      old_file "$MYSQL_PREFIX-$FILENAME"

      MYSQL=`$BINDIR/mysqldump -h $MYSQL_HOST -P $MYSQL_PORT -u $MYSQL_USER -p$MYSQL_PASS $MYSQL_OPT $DATANAME > $BACKUP_DEST/$MYSQL_PREFIX-$FILENAME-$DATE_FILE.sql 2>> $LOGDIR`
      if [ "$?" -ne 0 ]
      then
      MESSAGE="Erreur pendant l'export de la base $FILENAME"
      erreur "$MESSAGE"
      fi

      MESSAGE="Compression du fichier $MYSQL_PREFIX-$FILENAME-$DATE_FILE.sql"
      log "$MESSAGE"

      TAR=`tar $TAR_OPT $BACKUP_DEST/$MYSQL_PREFIX-$FILENAME-$DATE_FILE.tar.gz $BACKUP_DEST/$MYSQL_PREFIX-$FILENAME-$DATE_FILE.sql 2>> $LOGDIR`
      if [ "$?" -ne 0 ]
      then
      MESSAGE="Erreur pendant la compression du fichier $MYSQL_PREFIX-$FILENAME-$DATE_FILE.sql"
      erreur "$MESSAGE"
      fi

      MESSAGE="Suppression du fichier $MYSQL_PREFIX-$FILENAME-$DATE_FILE.sql"
      log "$MESSAGE"

      RM=`$BIN/rm -f $BACKUP_DEST/$MYSQL_PREFIX-$FILENAME-$DATE_FILE.sql 2>> $LOGDIR`
      if [ "$?" -ne 0 ]
      then
      MESSAGE="Erreur à la suppression du fichier $MYSQL_PREFIX-$FILENAME-$DATE_FILE.sql"
      erreur "$MESSAGE"
      fi
      I=`$BINDIR/expr $I + 1 2>> $LOGDIR`
      done
      delete_lock_file
      exit 0
      }

      # Creation du fichier temporaire
      LOGFILE=${mktemp -q}
      if [ "$?" -ne 0 ]
      then
      echo "$DATE_LOG $HOSTNAME backupeur[$$]: Création du fichier temporaire impossible" >> $LOGDIR
      exit 1
      fi

      case "$1" in
      "file") backup_file ;;
      "mysql") backup_mysql ;;
      "bande") backup_bande ;;
      "") no_parametre ;;
      "*") echo "toto" ;;
      esac

      Born to Kill EndUser !

    • [^] # Re: à l'envers ;-)

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

      Je continue ;)
      J'ai réussi à adapter ton idée à mon script mais j'ai un petit pour la récupération de la date/heure. Ton idée est de mettre dans un fichier les messages brut, ensuite de lire ce fichier, le modifier selon mes besoins et de l'écrire dans les logs. Ca marche nikel pour les messages que je génère puisqu'a la limite je les écrits directement dans les logs sous la forme plus ou moins standard. Au contraire pour les messages renvoyés par les commandes, par exemple
      TAR=`tar -chf /tmp/MonHome.tar.gz` 2>&1 > ${LOGFILE_TEMP}

      Ca va bien écrire les infos dans le fichier temporaire. Ensuite je relis le fichier temporaire par :
      # Ecriture des logs
      write_log() {
      date_log

      # Lit le fichier log temporaire
      # et l'écrit dans les vrais log ligne par ligne
      cat ${LOGFILE_TEMP} | while read LIGNE_ERREUR
      do
      echo "${DATE_LOG} ${HOSTNAME} backupeur[$$]: ${LIGNE_ERREUR}" >> ${LOGDIR}
      done
      }

      Le hic est au niveau de DATE_LOG, ca me récupère la date/heure du moment ou je lance write_log et non le moment où la ligne à été écrite dans le fichier temporaire. La solution, stocker dans ce fichier la date/heure d'écriture du message mais le hic est que je ne sais pas trop comment faire.

      Philippe.

      Born to Kill EndUser !

      • [^] # Re: à l'envers ;-)

        Posté par  . Évalué à 1.

        Bonjour,

        D'où mon idée "à l'envers" pour traiter ton problème :-)

        Après moult relectures (et à tête reposée), je n'ai pas été assez "clair" dans mon explication :/
        Voici ce que je te propose pour une commande lancée avec les bonnes redirections (cf message précédent) :
        1) lire les logs stderr "au fil de l'eau" pour les formatter comme bon te semble via une boucle while read
        2) renvoyer la sortie stdout (qui liste les fichiers archivés dans le tar) dans un fichier (temporaire).
        => Ainsi, tu formatteras chaque ligne d'erreur de la commande avec l'ajout du timestamp "au plus proche" de l'arrivé du message.
        Ensuite, tu récupères la liste des fichiers dans ta variable shell pour faire ton traitement dessus.

        [humour]
        Bien sûr, je ne peux te certifier de la précision de l'horodatage des messages car je n'ai pas prise sur l'ordonnancement des processus de ton serveur ^__^, mais, à priori, dans des conditions normales, cela devrait convenir :-)
        ... (à suivre)
        [/humour]

        Bon courage !

        Cdlt,

        [humour]
        ... (suite)
        PS: Si tu veux assurer un peu plus de précision, voici des pistes:
        1) augmenter virtuellement la priorité de ton shell en diminuant la priorité de la commande tar (man nice)
        Par exemple: nice -n 19 tar ... | while read
        2) passer à un Linux Temps Réel
        [/humour]
        • [^] # Re: à l'envers ;-)

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

          Bonjour, effectivement je n'avais pas compris ça comme ça ;)
          Au contraire c'est assez lourd au niveau du script puisque pour toute les commandes pouvant générer une erreur je dois faire le while read... N'y a t'il pas un moyen de renvoyer la sauce vers une fonction qui elle est chargé de faire le while ?

          Par exemple :
          TAR=`tar -czvf /tmp/MonHome.tar.gz /home/MonHome` 2>&1 > 'MaFonctionQuiVaBien'

          et la fonction
          MaFonctionQuiVaBien() {
          cat "${1}" | while read LIGNE_ERREUR
          do
          echo "${DATE_LOG} ${HOSTNAME} backupeur[$$]: ${LIGNE_ERREUR}" >> ${LOGDIR}
          done

          Born to Kill EndUser !

          • [^] # Re: à l'envers ;-)

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

            Je continue à creuser :
            Maintenant j'en suis à :
            HOSTNAME=`hostname`

            date_log () {
            DATE_LOG=`date +%b" "%d" "%T`
            }

            write_log() {
            date_log

            echo "${1}" | while read LIGNE_ERREUR do
            do
            echo "${DATE_LOG} ${HOSTNAME} backupeur[$$]: ligne :${LIGNE_ERREUR}" >> /tmp/tar.log
            done
            }

            date_log
            MKTEMP=$(mktemp -q)
            tar -czf /tmp/net.tar.gz /ne 2>&1 | write_log
            rm -f ${MKTEMP}


            Ca marche presque, après le | le script va bien dans la fonction write_log mais au contraire je ne sais pas comment lui faire comprendre au moment de l'appel que write_log doit prendre la sortie (out ou err) de tar. J'ai essayé en mettant tar dans une variable et ensuite de mettre ma variable en paramètre pour la fonction mais ça donne rien.

            Born to Kill EndUser !

            • [^] # Re: à l'envers ;-)

              Posté par  . Évalué à 1.

              Bonjour,

              Le fichier temporaire créé via mktemp sert à stocker (temporairement) la sortie de la commande, pour laquelle aucun traitement immédiat n'est nécessaire.
              Ainsi, après avoir "redirigé" les écritures sur 2(stderr) vers là où est écrit la sortie 1(stdout), tu peux faire de même en envoyant 1(stdout) vers le fichier temporaire.
              L'important est de conserver l'ordre des redirections sinon ca ne fait pas ce que l'on souhaite :-)

              Une fois que la commande est terminée et que write_log a fini, tu peux lire le contenu du fichier temporaire pour le charger dans une variable (dans ton exemple initial, il s'agissait de TAR).

              En espérant avoir pu éclaircir les dernières ambigüités de mon premier commentaire ^__^

              Bon courage !

              Cdlt,

              PS: Pour donner une solution à ton problème, voici le code que je propose et qui utilise un "wrapper" (A TESTER !).
              Par avance, je vous remercie pour votre indulgence:

              # Les constantes

              # HOSTNAME - Nom de la machine
              declare -r HOSTNAME="${HOSTNAME:-$(hostname)}"

              function execute_et_log_stderr {

              # Argument 1: Le fichier de log ou /dev/null :-)
              local -r log_stderr="${1:-/dev/null}"
              shift

              # Une ressource temporaire pour stdout
              local -r cmd_stdout=$(mktemp -q)
              if [ ${?} -ne 0 ]; then
              echo Impossible d"'"allouer une ressource temporaire '!'
              return 255
              fi

              ${@} 2>&1 1>"${cmd_stdout}" \
              | while read cmd_stderr; do
              echo "$(date +%b" "%d" "%T) ${HOSTNAME} backupeur[$$]: ligne :${cmd_stderr}"
              done >> ${log_stderr}

              cat ${cmd_stdout} && rm -f ${cmd_stdout}

              return 0
              }

              # La pas de log de stderr
              SANS_LOG=$(execute_et_log_stderr "" ls -al /home)

              # La, il y aura log de stderr dans ${LOGFILE}
              LOG_FILE=/var/log/backupeur
              AVEC_LOG=$(execute_et_log_stderr "${LOGFILE}" tar -czf /tmp/net.tar.gz /ne)



              PS2: Tiens, je revois presque le code proposé à mon premier post :P

              PS3: Promis, j'arrête ! C'est mon dernier post sur cette demande (H)

Suivre le flux des commentaires

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