Inoltra / reindirizza un gruppo di comandi


8

Attualmente utilizzo la seguente configurazione per reindirizzare l'output di più comandi:

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

Questo è piuttosto utile e funziona anche con le pipe.

È questo il modo migliore per farlo? C'è un'alternativa che dovrei considerare?


È così che lo farei. Stai riscontrando un problema particolare con questo approccio?
Bratchley,

@JoelDavis No, mi chiedevo solo se ci fosse un modo migliore per farlo. Dalle risposte che ho ricevuto, sembra che ci sia! :)
mercoledì

Risposte:


15

L'alternativa è usare le parentesi graffe anziché le parentesi. Questa modifica esegue i comandi nella shell corrente , non in una subshell

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

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

Ciò è particolarmente rilevante quando si modificano le variabili all'interno del gruppo:

$ 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

Eccellente grazie! Funziona meglio anche con il mio rientro. (Vim rientri { }ma non ( ).)
wchargin

2
Si noti che è possibile comprimere il gruppo di comandi in una sola riga in entrambi i modi; ad es. (echo msg1; echo msg2)- ma, con parentesi graffe, deve essere { echo msg1; echo msg2;}, con uno spazio dopo il {punto e un punto e virgola ( ;) o una e commerciale ( &) prima del }.
G-Man dice "Ripristina Monica" il

4

La risposta di Glenn è buona: la distinzione tra ( ... )ed { ... }è importante.

Una strategia che uso spesso per l'output di errori come quello che è nella tua domanda è il teecomando. Potresti fare qualcosa del genere:

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"

Il teecomando invierà l'output in due posizioni; -al'opzione "aggiunge" l'output al file indicato e il comando passerà anche l'input a stdout. Il >&2alla fine della riga reindirizza lo teestdout a stderr, che può essere gestito in modo diverso (cioè in un lavoro cron).

Un altro suggerimento che utilizzo spesso negli script di shell è quello di modificare il comportamento del debug o dell'output dettagliato in base al fatto che lo script sia in esecuzione su un terminale o abbia -vun'opzione fornita. Per esempio:

#!/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

Gli script possono iniziare con qualcosa di generico come questo nella parte superiore, con l'output di Verbose e Debug sparsi in tutto lo script. È solo un modo per farlo: ce ne sono molti e persone diverse avranno il loro modo di gestire queste cose, specialmente se sono in giro da un po '. :)

Un'altra opzione è gestire l'output con un "gestore", una funzione shell che può fare cose più intelligenti. Per esempio:

#!/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"

(Nota che ${var^^}è solo bash.)

Questo crea una funzione shell che può usare le syslogfunzioni del tuo sistema (con loggercomando ) to send things to system logs. Thelogme () `La funzione può essere usata sia con opzioni che generano singole linee di dati di registro, sia con più linee di input che vengono elaborate su stdin. sembra allettante.

Nota che questo è un esempio e probabilmente non dovrebbe essere copiato alla lettera se non lo capisci e sai che fa esattamente quello che ti serve. Un'idea migliore è prendere qui i concetti e implementarli da soli nei propri script.


Grazie mille per la tua risposta dettagliata! In realtà sto usando un function log () { cat >> $logfile }, che è fondamentalmente una versione più semplice della tua logme.
mercoledì

Inoltre, si potrebbe essere interessati a ts(1); utilizzo: { 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)non è installato sui sistemi che uso - FreeBSD, OSX e una vecchia scatola di Ubuntu. Puoi dirci cosa lo fornisce?
ghoti,

Fa parte di moreutils, che include anche alcuni bei strumenti come sponge(1)(scrivere su file solo dopo la chiusura di stdin, quindi è possibile fare a something < foo | sponge foomeno del blocco foodel reindirizzamento) e vipe(1)(inserire un editor di testo in una pipe).
wchargin,

1

Il modo più appropriato di farlo è con { command; }piuttosto che con (command). Il motivo è che quando i comandi sono raggruppati con ()una subshell viene aperta per eseguire quei comandi e quindi le variabili che sono inizializzate durante quel blocco non saranno disponibili per altre sezioni dello script.

Invece quando usiamo {}per il raggruppamento dei comandi, i comandi vengono eseguiti all'interno della stessa shell e quindi le variabili saranno disponibili per altre sezioni dello 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"

Qui, quando questa sezione viene eseguita, la $varvariabile mantiene il suo valore, mentre come nell'altro caso non lo farà.


Inoltre, quando si utilizza su una riga, ricordare di avere degli spazi in mezzo e terminare il comando con punto e virgola. Esempio: { command; }.
CMCDragonkai,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.