Glenn's answer is a good one -- the distinction between ( ... )
and { ... }
is important.
One strategy I often use for error output like what's in your question is the tee
command. You could do something like this:
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"
The tee
command will send output to two places; -a
option "appends" output to the named file, and the command will also pass input along to stdout. The >&2
at the end of the line redirects tee
's stdout to stderr, which may be handled differently (i.e. in a cron job).
One other tip that I often use in shell scripts is to change the behaviour of debug or verbose output based on whether the script is running on a terminal or has a -v
option provided. For example:
#!/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
Scripts can start with something generic like this at the top, with Verbose and Debug output spattered throughout the script. It's just one way to do it -- there are many, and different folks will all have their own way to handle this stuff, especially if they've been around a while. :)
One more option is to handle your output with a "handler" -- a shell function that may do more intelligent things. For example:
#!/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"
(Note that ${var^^}
is bash-only.)
This creates a shell function which may uses your system's syslog
functions (with logger
command) to send things to system logs. The
logme()` function can be used either with options that generate single lines of log data, or with multiple lines of input that are processed on stdin. Play with it if it seems appealing.
Note that this is an example and probably should not be copied verbatim unless you understand it and know that it does precisely what you need. A better idea is to take the concepts here and implement them yourself in your own scripts.
:)
– wchargin Oct 27 '14 at 18:10