Leiten Sie eine Gruppe von Befehlen weiter

8

Ich verwende derzeit das folgende Setup, um die Ausgabe mehrerer Befehle umzuleiten:

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

Dies ist ziemlich nützlich und funktioniert auch mit Rohren.

Ist das der beste Weg, dies zu tun? Gibt es eine Alternative, die ich in Betracht ziehen sollte?

wchargin
quelle
So würde ich es machen. Stoßen Sie bei diesem Ansatz auf ein bestimmtes Problem?
Bratchley
@ JoelDavis Nein, ich frage mich nur, ob es einen besseren Weg gibt, es zu tun. Aus den Antworten, die ich erhalten habe, geht hervor, dass es solche gibt! :)
Wchargin

Antworten:

15

Die Alternative besteht darin, Klammern anstelle von Klammern zu verwenden. Diese Änderung führt die Befehle in der aktuellen Shell aus, nicht in einer Subshell

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

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

Dies ist besonders relevant, wenn Sie Variablen innerhalb der Gruppe ändern:

$ 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
quelle
Großartig, danke! Dies funktioniert auch besser mit meiner Einrückung. (Vim Einrückungen, { }aber nicht ( ).)
wchargin
2
Beachten Sie, dass Sie die Befehlsgruppe auf beide Arten in eine einzelne Zeile reduzieren können. zB (echo msg1; echo msg2)- aber bei geschweiften Klammern muss es { echo msg1; echo msg2;}ein Leerzeichen nach dem {und ein Semikolon ( ;) oder ein kaufmännisches Und ( &) vor dem sein }.
G-Man sagt "Reinstate Monica"
4

Glenns Antwort ist gut - die Unterscheidung zwischen ( ... )und { ... }ist wichtig.

Eine Strategie, die ich häufig für die Fehlerausgabe verwende, wie z. B. Ihre Frage, ist der teeBefehl. Sie könnten so etwas tun:

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"

Der teeBefehl sendet die Ausgabe an zwei Stellen. -aDie Option "hängt" die Ausgabe an die benannte Datei an, und der Befehl leitet die Eingabe auch an stdout weiter. Das >&2am Ende der Zeile stehende teestdout leitet es an stderr weiter, was möglicherweise anders gehandhabt wird (dh in einem Cron-Job).

Ein weiterer Tipp, den ich häufig in Shell-Skripten verwende, besteht darin, das Verhalten der Debug- oder ausführlichen Ausgabe zu ändern, je nachdem, ob das Skript auf einem Terminal ausgeführt wird oder über eine -vOption verfügt. Zum Beispiel:

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

Skripte können mit etwas Allgemeinem wie diesem oben beginnen, wobei die Ausgabe von Verbose und Debug im gesamten Skript verteilt ist. Es ist nur eine Möglichkeit, dies zu tun - es gibt viele, und verschiedene Leute werden alle ihre eigene Art haben, mit diesen Dingen umzugehen, besonders wenn sie schon eine Weile da sind. :) :)

Eine weitere Option besteht darin, Ihre Ausgabe mit einem "Handler" zu verarbeiten - einer Shell-Funktion, die möglicherweise intelligentere Aufgaben ausführt. Zum Beispiel:

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

(Beachten Sie, dass dies ${var^^}nur Bash ist.)

Dadurch wird eine Shell-Funktion erstellt, die möglicherweise die syslogFunktionen Ihres Systems verwendet (mit dem loggerBefehl ) to send things to system logs. Thelogme (). Die Funktion kann entweder mit Optionen verwendet werden, die einzelne Zeilen von Protokolldaten generieren, oder mit mehreren Eingabezeilen, die auf stdin verarbeitet werden. Spielen Sie damit, wenn dies der Fall ist scheint ansprechend.

Beachten Sie, dass dies ein Beispiel ist und wahrscheinlich nicht wörtlich kopiert werden sollte, es sei denn, Sie verstehen es und wissen, dass es genau das tut, was Sie brauchen. Eine bessere Idee ist es, die Konzepte hier zu übernehmen und sie selbst in Ihren eigenen Skripten zu implementieren.

Ghoti
quelle
Vielen Dank für Ihre ausführliche Antwort! Ich habe tatsächlich eine verwendet function log () { cat >> $logfile }, die im Grunde eine einfachere Version von Ihrer ist logme.
wchargin
Man könnte auch interessiert sein an ts(1); Verwendung : { 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)ist nicht auf den von mir verwendeten Systemen installiert - FreeBSD, OSX und eine alte Ubuntu-Box. Können Sie uns sagen, was es bietet?
Ghoti
Es ist Teil von moreutils, das auch einige nette Tools enthält, wie sponge(1)(erst nach dem Schließen von stdin in eine Datei schreiben, damit Sie nicht durch Umleitung something < foo | sponge fooüberladen können foo) und vipe(1)(einen Texteditor in eine Pipe einfügen).
wchargin
1

Ein geeigneterer Weg, dies zu tun, ist { command; }eher mit als (command). Der Grund dafür ist, dass beim Gruppieren von Befehlen mit ()einer Subshell diese Befehle ausgeführt werden und die Variablen, die während dieses Blocks initialisiert werden, anderen Abschnitten des Skripts nicht zur Verfügung stehen.

Wenn wir stattdessen {}für die Befehlsgruppierung verwenden, werden die Befehle in derselben Shell ausgeführt, sodass die Variablen anderen Abschnitten des Skripts zur Verfügung stehen.

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"

Wenn dieser Abschnitt ausgeführt $varwird, behält die Variable hier ihren Wert bei, wo dies im anderen Fall nicht der Fall ist.

Kannan Mohan
quelle
Denken Sie auch bei der Verwendung in einer Zeile daran, Leerzeichen dazwischen zu haben, und beenden Sie den Befehl mit einem Semikolon. Beispiel : { command; }.
CMCDragonkai