web-dev-qa-db-fra.com

Comment puis-je enregistrer entièrement toutes les actions des scripts bash?

À partir de la sortie de mon script, je veux capturer TOUTES les données des journaux avec des messages d'erreur et les rediriger toutes vers le fichier journal.

J'ai un script comme ci-dessous:

#!/bin/bash
(
echo " `date` : part 1 - start "
ssh -f [email protected] 'bash /www/htdocs/server.com/scripts/part1.sh logout exit'
echo " `date` : sleep 120"
sleep 120
echo " `date` : part 2 - start"
ssh [email protected] 'bash /www/htdocs/server.com/scripts/part2.sh logout exit'
echo " `date` : part 3 - start"
ssh [email protected] 'bash /www/htdocs/server.com/scripts/part3.sh logout exit'
echo " `date` : END"
) | tee -a /home/scripts/cron/logs

Je veux voir toutes les actions dans le fichier /home/scripts/cron/logs

Mais je ne vois que ce que j'ai mis après la commande echo.

Comment vérifier dans les journaux que la commande SSH a réussi?

J'ai besoin de rassembler tous les journaux. J'en ai besoin pour surveiller le résultat de chaque commande de mon script, pour mieux analyser ce qui se passe pendant que le script échoue.

50
BlueMark

Je mets généralement quelque chose de similaire au suivant au début de chaque script (surtout s'il s'exécute en tant que démon):

#!/bin/bash
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>log.out 2>&1
# Everything below will go to the file 'log.out':

Explication:

  1. exec 3>&1 4>&2

    Enregistre les descripteurs de fichiers afin qu'ils puissent être restaurés à ce qu'ils étaient avant la redirection ou utilisés eux-mêmes pour sortir vers ce qu'ils étaient avant la redirection suivante.

  2. trap 'exec 2>&4 1>&3' 0 1 2 3

    Restaurer des descripteurs de fichiers pour des signaux particuliers. Pas généralement nécessaire car ils doivent être restaurés à la sortie du sous-shell.

  3. exec 1>log.out 2>&1

    Rediriger stdout vers le fichier log.out puis redirigez stderr vers stdout. Notez que l'ordre est important lorsque vous souhaitez qu'ils aillent dans le même fichier. stdoutdoit être redirigé avant que stderr soit redirigé vers stdout.

À partir de là, pour voir la sortie sur la console (peut-être), vous pouvez simplement rediriger vers &3. Par exemple,

echo "$(date) : part 1 - start" >&3

ira à l'endroit où stdout a été dirigé, probablement la console, avant d'exécuter la ligne 3 ci-dessus.

77
nicerobot

pour obtenir la sortie ssh dans votre fichier journal, vous devez rediriger stderr vers stdout. vous pouvez le faire en ajoutant 2>&1 après votre script bash.

ça devrait ressembler à ça:

#!/bin/bash
(
...
) 2>&1 | tee ...

lorsque cela n'affiche pas les messages dans le bon ordre, essayez d'ajouter un autre sous-shell:

#!/bin/bash
((
...
) 2>&1) | tee ...
16
Christian

En lisant votre question, vous ne voulez pas enregistrer la sortie, mais toute la séquence de commandes, dans ce cas, les autres réponses ne vous aideront pas.

Appelez des scripts Shell avec -x pour tout afficher:

sh -x foo.sh

Connectez-vous au fichier que vous souhaitez avec:

sh -x foo.sh >> /home/scripts/cron/logs

15
Alex Holst

En bash, vous pouvez mettre set -x et il imprimera toutes les commandes qu'il exécute (et les variables bash) après cela. Vous pouvez le désactiver avec set +x.

Si vous voulez être paranoïaque, vous pouvez mettre set -o errexit dans votre script. Cela signifie que le script échouera et s'arrêtera si une commande a renvoyé un code de sortie différent de zéro, qui est le moyen standard unix de signaler que quelque chose s'est mal passé.

Si vous voulez obtenir de meilleurs journaux, vous devriez regarder ts dans le paquet moreutils debian/ubuntu. Il préfixera chaque ligne avec un horodatage et l'imprimera. Ainsi, vous pouvez voir quand les choses se produisent.

10
Rory

J'ai trouvé que la réponse @nicerobot ( Comment puis-je enregistrer toutes les actions des scripts bash? ) pourrait ne pas suffire pour complètement rediriger toutes les sorties vers la console. Certains résultats peuvent encore être perdus.

La redirection complète ressemble à ceci:

#!/bin/bash

# some basic initialization steps including `NEST_LVL` and `SCRIPTS_LOGS_ROOT variables...
source ".../__myinit__.sh"

# no local logging if nested call
(( ! IMPL_MODE && ! NEST_LVL )) && {
  export IMPL_MODE=1
  exec 3>&1 4>&2
  trap 'exec 2>&4 1>&3' EXIT HUP INT QUIT RETURN

  [[ ! -e "${SCRIPTS_LOGS_ROOT}/.log" ]] && mkdir "${SCRIPTS_LOGS_ROOT}/.log"

  # RANDOM instead of milliseconds
  case $BASH_VERSION in
    # < 4.2
    [123].* | 4.[01] | 4.0* | 4.1[^0-9]*)
      LOG_FILE_NAME_SUFFIX=$(date "+%Y'%m'%d_%H'%M'%S''")$(( RANDOM % 1000 ))
      ;;
    # >= 4.2
    *)
      printf -v LOG_FILE_NAME_SUFFIX "%(%Y'%m'%d_%H'%M'%S'')T$(( RANDOM % 1000 ))" -1
      ;;
  esac

  (
  (
    myfoo1
    #...
    myfooN

    # self script reentrance...
    exec $0 "$@"
  ) | tee -a "${SCRIPTS_LOGS_ROOT}/.log/${LOG_FILE_NAME_SUFFIX}.myscript.log" 2>&1
  ) 1>&3 2>&4

  exit $?
}

(( NEST_LVL++ ))

# usual script body goes here...

Explication :

  1. Il ne suffit pas de rediriger la sortie car vous devez toujours rediriger les appels internes echo vers la console et vers le fichier, donc le pipe-plus-tee est le seul moyen de diviser le flux de sortie.
  2. Nous devons utiliser (...) pour rediriger chaque flux vers un fichier autonome ou pour restaurer tous les flux vers l'original par des étapes distinctes. La deuxième raison parce que myfoo appelle avant l'auto-réentrée doit fonctionner tel quel sans aucune redirection supplémentaire.
  3. Étant donné que le script peut être appelé à partir d'un script imbriqué, nous devons rediriger la sortie dans un fichier une seule fois lors d'un appel de niveau supérieur. Nous utilisons donc NEST_LVL variable pour indiquer le niveau d'appel imbriqué.
  4. Étant donné que la redirection peut être exécutée en externe indépendamment du script, nous devons alors activer explicitement la redirection interne par le IMPL_MODE variable.
  5. Nous devons utiliser des valeurs de date et d'heure pour créer automatiquement un nouveau fichier journal unique à chaque exécution de script.
1
Andry

Suite à ce que les autres ont dit, l'ensemble manuel est une bonne ressource. Je mets:

#!/usr/bin/env bash
exec 1> command.log 2>&1
set -x

En haut des scripts, je souhaite continuer, ou set -ex s'il doit sortir en cas d'erreur.

1
dragon951

TL; DR - Hier, j'ai écrit un ensemble d'outils pour enregistrer les exécutions et les sessions de programme.

Actuellement disponible sur https://github.com/wwalker/quick-log

En tant qu'administrateur, je veux toujours enregistrer la sortie d'une commande, souvent pas un script. Pour résoudre ce problème, j'ai écrit quelques choses. Le plus simple est d'utiliser le programme "script" comme mentionné xX0v0Xx. J'ai trouvé que l'appel de script (sans aucun argument) me faisait souvent écraser la sortie d'un script précédent. J'ai donc créé cet alias. Il ne fait qu'empêcher l'écrasement. Vous avez besoin d'un répertoire ~/tmp.

$ alias scr='script ~/tmp/TypeScript-$(date +%FT%T)'
$ scr
Script started, file is /home/wwalker/tmp/TypeScript-2019-12-05T18:56:31
$

C'est super quand je veux assister à une session interactive.

Lorsque je veux enregistrer la sortie d'une commande (script ou binaire), je veux soit la sortie exacte, soit la sortie avec des horodatages devant chaque ligne. J'ai donc écrit ces deux fonctions bash:

alias iso8601="date +%Y-%m-%dT%H:%M:%S"

justlog(){
  name=$(basename "$1")
  log=~/logs/${name}-$(iso8601)
  "$@" > "$log" 2>&1
}

timelog(){
  name=$(basename "$1")
  log=~/logs/${name}-$(iso8601)
  # https://github.com/wwalker/ilts
  # You could replace ilts with ts
  # /usr/bin/ts %FT%H:%M:%.S
  "$@" |& ilts -S -E > "$log" 2>&1
}

Exécutez simplement votre commande comme vous le feriez normalement:

justlog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'

ou

timelog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'

Cela vient de créer 2 fichiers appelés:

wwalker@polonium:~ ✓ $ ls -l ~/logs/run*
-rw-r--r-- 1 wwalker wwalker 10495 2019-12-05 18:21:14.985 /home/wwalker/logs/run-2019-12-05T18:21:13
-rw-r--r-- 1 wwalker wwalker  1694 2019-12-05 18:24:02.878 /home/wwalker/logs/run-2019-12-05T18:24:01
-rw-r--r-- 1 wwalker wwalker  7623 2019-12-05 18:25:07.873 /home/wwalker/logs/run-2019-12-05T18:25:06
-rw-r--r-- 1 wwalker wwalker 10296 2019-12-05 18:34:59.546 /home/wwalker/logs/run-2019-12-05T18:34:57

Mais attendez, il y a plus!

Je ne voulais pas avoir à faire un ls pour trouver le nom du fichier journal que justlog ou timelog venait de créer pour moi. J'ai donc ajouté 3 fonctions supplémentaires:

newestlog(){
  name=$(basename "$1")
  ls  ~/logs/"${name}"* | tail -1
}

viewlog(){
  name=$(basename "$1")
  view $( newestlog "$name" )
}

lesslog(){
  name=$(basename "$1")
  less $( newestlog "$name" )
}

Donc, vous exécutez votre commande avec justlog (ou timelog), puis vous utilisez simplement lesslog ou viewlog (je vais probablement créer un journal emacs pour ces personnes):

justlog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'
lesslog run

C'est ça, non ls ~/tmp, pas de jeux de tabulation pour trouver le nom du fichier. Exécutez simplement lesslog (ou viewlog si vous aimez utiliser vim pour consulter les journaux).

Mais attendez! Il y a plus!

"J'utilise grep tout le temps sur mes fichiers journaux" - Et la réponse est, vous l'avez deviné, greplog

Tout d'abord, récupérez le texte des fichiers /etc/cron.d/atop du serveur 800 qui sont cassés:

justlog fordcrun 'cat /etc/cron.d/atop; md5dum /etc/cron.d/atop'

Récupérez ensuite les noms d'hôtes (sur la ligne au dessus de la sortie du fichier :-)) avec greplog:

wwalker@polonium:~ ✓ $ greplog fordcrun  -B1 -F "0 0 * * root"
int-salt-01:
    0 0 * * root systemctl restart atop
--
rcn-pg-01:
    0 0 * * root systemctl restart atop
rcn-pg-02:
    0 0 * * root systemctl restart atop
rcn-pg-03:
    0 0 * * root systemctl restart atop
0
Wayne Walker