#! /bin/sh ### ### Run a program, but stash its output unless it fails ### ### (c) 2011 Mark Wooding ### ###----- Licensing notice --------------------------------------------------- ### ### This program is free software; you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation; either version 2 of the License, or ### (at your option) any later version. ### ### This program is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with this program; if not, write to the Free Software ### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA set -e quis=${0##*/} usage="usage: $quis [-d DIR] [-m EMAIL] [-n NLOG] TAG COMMAND [ARGS ...]" ver="@VERSION@" version () { echo "$quis, @PACKAGE@ version $ver"; } ###-------------------------------------------------------------------------- ### Parse the command line. ## Initialize variables for storing command-line option values. logdir="@logdir@" maxlog=16 unset mail unset owner unset mode ## Scan the options. while getopts "hvd:m:n:p:u:" opt; do case "$opt" in h) version cat <&2 "$usage"; exit 1 ;; esac done shift $(( $OPTIND - 1 )) ## Check the arguments. case $# in 0 | 1) echo >&2 "$usage"; exit 1 ;; esac tag=$1 cmd=$2; shift 2 ###-------------------------------------------------------------------------- ### Check out the environment. ## Force a command to line-buffer its output. How does one do this on BSD, ## for example? if stdbuf --version >/dev/null 2>&1; then lbuf="stdbuf -oL --" else lbuf="" fi ###-------------------------------------------------------------------------- ### Set up the log file. ## Find a name for the log file. In unusual circumstances, we may have ## deleted old logs from today, so just checking for an unused sequence ## number is insufficient. Instead, check all of the logfiles for today, and ## use a sequence number that's larger than any of them. date=$(date +%Y-%m-%d) seq=1 for i in "$logdir/$tag.$date#"*; do tail=${i##*#} case "$tail" in [!1-9]* | *[!0-9]*) continue ;; esac if [ -f "$i" -a $tail -ge $seq ]; then seq=$(( $tail + 1 )); fi done log="$logdir/$tag.$date#$seq" ## Create the file. Make sure we create it with restrictive permissions ## and then slacken them off if necessary. This means that we don't (for ## example) end up giving the wrong group write permission to the file for a ## little bit. umask=$(umask) case ${mode+t} in t) ;; *) mode=$(printf %o $(( 0666 & ~$umask ))) ;; esac umask 077; exec 3>"$log"; umask $umask case ${owner+t} in t) chown "$owner" "$log" ;; esac chmod $mode "$log" ###-------------------------------------------------------------------------- ### Run the program. ## Write a log header. cat >&3 <&- 4>&- 5>&-; echo $? >&5; } | copy "|" >&4; } 2>&1 | copy "*" >&4; } 4>&1 | cat -u >&3; } 5>&1 &3 <&- ###-------------------------------------------------------------------------- ### Delete old log files if there are too many. ## Find out the tails of the logfile names. We assume that we're responsible ## for all of these, and therefore that they're nicely formed. logs="" nlog=0 for i in "$logdir/$tag".*; do if [ ! -f "$i" ]; then continue; fi nlog=$(( $nlog + 1 )) logs="$logs ${i#$logdir/$tag.}" done ## If there are too many, go through and delete some early ones. if [ $nlog -gt $maxlog ]; then n=$(( $nlog - $maxlog )) for i in $logs; do echo $i; done | sort -t# -k1,1 -k2n | while read i; do rm -f "$logdir/$tag.$i" n=$(( $n - 1 )) if [ $n -eq 0 ]; then break; fi done fi ###-------------------------------------------------------------------------- ### Do something useful with the result. case $rc,${mail+t} in 0,*) ## Everything worked. Leave the results in the log file in case someone ## cares. ;; *,t) ## Failed, and we have an email address. Send mail and appear to ## succeed: we've done our job and reported the situation. The idea is ## to prevent something else (e.g., cron) from producing another report ## for the same problem, but without the useful content. mail -s "$tag: $cmd failed (status = $rc)" "$mail" <"$log" rc=0 ;; *) ## Failed, and no email address. Write the accumulated stuff. cat "$log" ;; esac ## Exit with an appropriate status. exit $rc ###----- That's all, folks --------------------------------------------------