Canaliser / rediriger un groupe de commandes

8

J'utilise actuellement la configuration suivante pour rediriger la sortie de plusieurs commandes:

echo "Some normal commands"
(
echo "Error: something happened"
echo "Warning: this incident will be logged"
) >> logfile
echo "More normal commands"

C'est assez utile, et cela fonctionne également avec les tuyaux.

Est-ce la meilleure façon de procéder? Y a-t-il une alternative à considérer?

wchargin
la source
C'est comme ça que je ferais. Rencontrez-vous un problème particulier avec cette approche?
Bratchley
@JoelDavis Non, je me demandais simplement s'il y avait une meilleure façon de le faire. D'après les réponses que j'ai reçues, il semble qu'il y en ait! :)
wchargin

Réponses:

15

L'alternative consiste à utiliser des accolades au lieu de parenthèses. Cette modification exécute les commandes dans le shell actuel , pas dans un sous - shell

echo "Some normal commands"
{
echo "Error: something happened"
echo "Warning: this incident will be logged"
} >> logfile
echo "More normal commands"

réf: https://www.gnu.org/software/bash/manual/bashref.html#Command-Grouping

Ceci est particulièrement pertinent lorsque vous modifiez des variables à l'intérieur du groupe:

$ x=5; ( x=10; echo inside: $x; ); echo outside: $x
inside: 10
outside: 5

$ x=5; { x=10; echo inside: $x; }; echo outside: $x
inside: 10
outside: 10
glenn jackman
la source
Excellent, merci! Cela fonctionne mieux avec mon retrait également. (Vim met en retrait { }mais pas ( ).)
wchargin
2
Notez que vous pouvez réduire le groupe de commandes sur une seule ligne dans les deux sens; par exemple, (echo msg1; echo msg2)- mais, avec des accolades, il doit être { echo msg1; echo msg2;}, avec un espace après le {et un point-virgule ( ;) ou une esperluette ( &) avant le }.
G-Man dit `` Réintègre Monica ''
4

La réponse de Glenn est bonne - la distinction entre ( ... )et { ... }est importante.

Une stratégie que j'utilise souvent pour la sortie d'erreur comme ce qui est dans votre question est la teecommande. Vous pourriez faire quelque chose comme ça:

echo "Normal output"
{
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "Warning text"
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "This event is logged."
} | tee -a $logfile >&2
echo "More normal output"

La teecommande enverra la sortie à deux endroits; -al'option "ajoute" la sortie au fichier nommé, et la commande passera également l'entrée à stdout. La >&2fin de la ligne redirige la sortie teestandard de stdout vers stderr, qui peut être traitée différemment (c'est-à-dire dans une tâche cron).

Une autre astuce que j'utilise souvent dans les scripts shell est de changer le comportement du débogage ou de la sortie détaillée selon que le script s'exécute sur un terminal ou dispose d'une -voption. Par exemple:

#!/bin/sh

# Set defaults
if [ -t 0 ]; then
  Verbose=true; vflag="-v"
else
  Verbose=false; vflag=""
fi
Debug=false; AskYN=true; Doit=true

# Detect options (altering defaults)
while getopts vdqbn opt; do
  case "$opt" in
    v)  Verbose=true; vflag="-v" ;;             # Verbose mode
    d)  Debug=true; Verbose=true; vflag="-v" ;; # Very Verbose
    q)  Verbose=false; vflag="" ;;              # quiet mode (non-verbose)
    b)  AskYN=false ;;                          # batch mode
    n)  Doit=false ;;                           # test mode
    *)  usage; exit 1 ;;
  esac
done

# Shift our options for further processing
shift $(($OPTIND - 1))

$Verbose && echo "INFO: Verbose output is turned on." >&2
$Debug && echo "INFO: In fact, expect to be overrun." >&2

# Do your thing here
if $AskYN; then
  read -p "Continue? " choice
  case "$choice" in
    Y|y) $Doit && somecommand ;;
    *) echo "Done." ;;
  esac
fi

Les scripts peuvent commencer par quelque chose de générique comme celui-ci en haut, avec des sorties verbeuses et de débogage dispersées tout au long du script. C'est juste une façon de le faire - il y en a beaucoup, et différentes personnes auront toutes leur propre façon de gérer ce genre de choses, surtout si elles existent depuis un certain temps. :)

Une autre option consiste à gérer votre sortie avec un "gestionnaire" - une fonction shell qui peut faire des choses plus intelligentes. Par exemple:

#!/bin/bash

logme() {
  case "${1^^}" in
    [IN]*)  level=notice ;;
    W*)     level=warning ;;
    A*)     level=alert ;;
    E*)     level=emerg ;;
    *)      level=notice ;;
  esac
  if [[ "$#" -eq 1 ]]; then
    # Strip off unnecessary prefixes like "INFO:"
    string="${1#+([A-Z])?(:) }"
  else
    shift
    string="$@"
  fi
  logger -p "${facility}.${level}" -t "$(hostname -s)" "$string"
}

echo "Normal output"
logme INFO "Here we go..."
somecommand | logme
echo "Additional normal output"

(Notez que ${var^^}c'est uniquement bash.)

Cela crée une fonction shell qui peut utiliser les syslogfonctions de votre système (avec la loggercommande ) to send things to system logs. Thelogme () `la fonction peut être utilisée soit avec des options qui génèrent des lignes de données de journal uniques, soit avec plusieurs lignes d'entrée qui sont traitées sur stdin. Jouez avec si elle semble attrayant.

Notez que ceci est un exemple et ne devrait probablement pas être copié mot pour mot à moins que vous ne le compreniez et sachiez qu'il fait exactement ce dont vous avez besoin. Une meilleure idée est de prendre les concepts ici et de les implémenter vous-même dans vos propres scripts.

ghoti
la source
Merci beaucoup pour votre réponse détaillée! J'ai en fait utilisé un function log () { cat >> $logfile }, qui est essentiellement une version plus simple de votre logme.
wchargin
Aussi, on peut être intéressé par ts(1); utilisation: { printf '%s\n' "Warning text"; printf '%s\n' "This event will be logged"; } | ts '[%Y-%m-%d %T]' | tee -a "$logfile" >&2.
wchargin
@wchargin - ts(1)n'est pas installé sur les systèmes que j'utilise - FreeBSD, OSX et une vieille boîte Ubuntu. Pouvez-vous nous dire ce qui le fournit?
ghoti
Cela fait partie de moreutils, qui comprend également de bons outils comme sponge(1)(écrire dans le fichier uniquement après la fermeture de stdin, vous pouvez donc vous passer de something < foo | sponge fooclobber foopar redirection), et vipe(1)(insérer un éditeur de texte dans un tuyau).
wchargin
1

La manière la plus appropriée de le faire est { command; }plutôt que (command). La raison en est que lorsque les commandes sont regroupées avec ()un sous-shell est ouvert pour exécuter ces commandes et donc les variables qui sont initialisées pendant ce bloc ne seront pas disponibles pour les autres sections du script.

Au lieu de cela, lorsque nous utilisons {}pour le regroupement de commandes, les commandes sont exécutées dans le même shell et donc les variables seront disponibles pour d'autres sections du script.

echo "Some normal commands"

{
    var=1
    echo "Error: something happened"
    echo "Warning: this incident will be logged"
} >> logfile

echo "The value of var is: $var"
echo "More normal commands"

Ici, lorsque cette section est exécutée, la $varvariable conserve sa valeur, ce qui n'est pas le cas dans l'autre cas.

Kannan Mohan
la source
De plus, lorsque vous utilisez sur une seule ligne, n'oubliez pas d'avoir des espaces entre les deux et terminez la commande par un point-virgule. Exemple: { command; }.
CMCDragonkai