- 
#!/bin/bash 
- 
- 
- 
# Created on: <2001-08-08 11:56:03 hackerb9> 
- 
# Time-stamp: <2002-11-20 14:13:11 bbb> 
- 
- 
# onlogdo: a hack to handle a common problem: when a certain message 
- 
# shows up in the log file, an action should be performed. E.g.: when 
- 
# a PC-card network device is inserted, it should be ifconfig’d or 
- 
# perhaps dhclient should be run. This program can do that in a fairly 
- 
# reasonable way. 
- 
- 
- 
- 
### FIRST, THE HELPER FUNCTIONS ### 
- 
- 
function showmanpage () {
 
- 
    cat <<‘EOF’
 
- 
NAME 
- 
- 
   onlogdo – monitor a log file and execute commands based on patterns 
- 
- 
- 
SYNOPSIS 
- 
- 
   onlogdo <logfile> <pattern> <command> [ <pattern> <command> … ]
 
- 
- 
     logfile: a file that gets appended to (e.g., /var/log/messages), 
- 
     pattern: a string (can use bash‘s extended pathname globbing syntax),
 
- 
     command: the command to run when the previous pattern is seen. 
- 
- 
- 
DESCRIPTION 
- 
- 
    Onlogdo is a hack to handle a common problem: when a certain 
- 
    message shows up in a log file, an action should be performed. 
- 
    E.g.: when a PC-card network device is inserted, it should be 
- 
    ifconfig’d or perhaps dhclient should be run. This program can do
 
- 
    that in a fairly reasonable way. 
- 
- 
    Onlogdo continuously reads lines as they are appended to a log 
- 
    file. When a line matches a given pattern, the command associated 
- 
    with that pattern is run in the background. The patterns 
- 
    recognized are standard pathname globbing (*, ?, []) plus bash‘s
 
- 
    extended pattern matching (extglob) syntax (see bash(1)). 
- 
- 
    The log file is reopened properly, if the log file being read is 
- 
    rotated. Also, since it blocks when waiting for new lines, onlogdo 
- 
    takes up almost no CPU time. 
- 
- 
- 
PATTERN MATCHING SYNTAX 
- 
- 
    Don’t worry about learning the pattern matching the first time you 
- 
    read this manual; you‘ll rarely need it, unless you’re anal about 
- 
    being as succinct as possible. (Like the author). 
- 
- 
    The following is an excerpt from bash‘s man page; see bash(1) for
 
- 
    full details. In the following description, a pattern-list is a 
- 
    list of one or more patterns separated by a |. Composite patterns 
- 
    may be formed using one or more of the following sub-patterns: 
- 
- 
       *      Matches any string, including the null string. 
- 
- 
       ?      Matches any single character. 
- 
- 
       […]  Matches any one of the enclosed characters. A pair of 
- 
              characters separated by a hyphen denotes a range 
- 
              expression. If the first character following the [ is a 
- 
              ! or a ^ then any character not enclosed is matched. 
- 
- 
      ?(pattern-list) 
- 
             Matches zero or one occurrence of the  given patterns 
- 
      *(pattern-list) 
- 
             Matches  zero  or  more  occurrences  of the given patterns 
- 
      +(pattern-list) 
- 
             Matches one or more occurrences of the given patterns 
- 
      @(pattern-list) 
- 
             Matches exactly one of the given patterns 
- 
      !(pattern-list) 
- 
             Matches  anything  except  one  of the given patterns 
- 
- 
- 
EXAMPLES 
- 
- 
    Pipelines are okay: 
- 
      onlogdo /var/log/messages ‘*‘ ‘sleep 5; echo olleH | rev‘
 
- 
- 
    Wildcards match filenames as usual in a command: 
- 
      onlogdo /var/log/messages ‘Segfault‘ ‘rm /*.core‘
 
- 
- 
    Multiple pattern and command pairs are allowed: 
- 
      onlogdo /var/log/messages \ 
- 
            ‘ep0 at pcmcia‘ ‘/etc/rc.d/dhclient start‘ \
 
- 
            ‘ep0 detached‘  ‘/etc/rc.d/dhclient stop‘  \
 
- 
            ‘wi0 at pcmcia‘ ‘/etc/rc.d/dhclient start‘ \
 
- 
            ‘wi0 detached‘  ‘/etc/rc.d/dhclient stop‘
 
- 
- 
    Bash’s extended pathname globbing to do the same as above: 
- 
      onlogdo /var/log/messages \ 
- 
            ‘@(ep|wi)[0-9] at pcmcia’ ‘/etc/rc.d/dhclient start’ \ 
- 
            ‘@(ep|wi)[0-9] detached’  ‘/etc/rc.d/dhclient stop’
 
- 
- 
    A useful example for NetBSD/hpcmips: 
- 
      onlogdo /var/log/messages ‘hpcapm: resume’ ‘sleep 1; xrefresh’
 
- 
- 
    Under NetBSD/hpcmips-1.5.1, the X server screen is overwritten by the 
- 
    console on an APM resume. I put the last “onlogdo” example in my 
- 
    system xinitrc, so xrefresh will be run automatically. (The sleep
 
- 
    is there to wait for the console to finish junking up the screen). 
- 
- 
- 
SEE ALSO 
- 
- 
    tail(1), bash(1)
 
- 
- 
BUGS 
- 
- 
    If this had been written in Perl it would have used “normal”
 
- 
    regexp syntax instead of pathname expansion. Bash‘s extensions
 
- 
    make the patterns as powerful as regexps, but more recondite. 
- 
- 
    There is no (documented) way for a command to refer to specifics 
- 
    in the line that matched. E.g., if the pattern was “@(ep|wi)0”, 
- 
    the command wouldn’t know if the line contained ep0 or wi0. 
- 
- 
    The author has spent way too much time perfecting a kludge. No 
- 
    matter what you‘re using onlogdo for, there’s probably a more
 
- 
    “correct” way to do it. On the other hand, onlogdo is widely 
- 
    applicable and easy to use, so at least you won‘t be wasting much
 
- 
    time while doing it the “wrong” way. 
- 
- 
    There should be a real man page. 
- 
- 
- 
AUTHOR 
- 
- 
- 
- 
- 
HISTORY 
- 
- 
    Onlogdo started life as a one line kludge to work around a bug in 
- 
    the interaction between APM and the X server in NetBSD-1.5/hpcmips 
- 
    in September of 2001. 
- 
- 
EOF 
- 
} 
- 
- 
- 
- 
- 
- 
### USAGE AND ARGUMENT SANITY CHECKING ### 
- 
- 
if [[ $# -lt 3 ]]; then 
- 
    showmanpage 
- 
    exit 1 
- 
fi 
- 
- 
# Emacs’s syntax highlighting doesn‘t handle single ticks (‘) properly. 
- 
- 
- 
- 
# Check if -v (verbose) flag was given. 
- 
if [[ “$1” == “-v” ]]; then
 
- 
    ifverbose=echo            # Echo commands verbosely, if -v.
 
- 
    shift
 
- 
else 
- 
    ifverbose=:   # Usually just run a no-op.
 
- 
fi 
- 
- 
- 
# Make sure the log file is readable. 
- 
if [[ ! -r “$1” ]]; then
 
- 
    echo “onlogdo: \”$1\” is not readable” >&2
 
- 
    exit 1
 
- 
fi 
- 
if [[ -d “$1” ]]; then
 
- 
    echo “onlogdo: \”$1\” is a directory” >&2
 
- 
    exit 1
 
- 
fi 
- 
- 
- 
- 
- 
- 
- 
### SIGNAL HANDLING AND EXIT CLEANUP ### 
- 
- 
# Run cleanup function when shell exits. 
- 
trap cleanup EXIT 
- 
- 
- 
cleanup () {
 
- 
    echo “onlogdo: cleaning up…” >&2
 
- 
    if [[ “$TAILPID” ]]; then
 
- 
        kill $TAILPID      # Kill the bg tail process.
 
- 
    fi
 
- 
    rm -f “$PIPE”              # Delete the named pipe.
 
- 
    rmdir “$PIPEDIR”
 
- 
    return 0
 
- 
} 
- 
- 
- 
- 
- 
### MAIN ROUTINE STARTS HERE ### 
- 
- 
# Don’t expand wildcards in pattern arguments as filenames. 
- 
set -o noglob 
- 
- 
# Use bash’s extended pattern matching operators 
- 
shopt -s extglob 
- 
- 
# Create a pipe to read the log file a line at a time;  
- 
# Use mktemp for security. 
- 
- 
PIPEDIR=$(mktemp -d /tmp/onlogdo.XXXXXX)
 
- 
PIPE=“$PIPEDIR/$(echo $1 | sed ‘s#^/##; s#/#.#g’)”      # var.log.messages
 
- 
rm -f $PIPE
 
- 
mknod $PIPE p 
- 
tail -Fn0 $1 >$PIPE &      # FSF tail: “tail –retry -fn0”
 
- 
TAILPID=$!
 
- 
exec 5< $PIPE
 
- 
- 
# Copy the positional paramaters in to a variable that can be indexed. 
- 
params=(“$0” “$@”)
 
- 
- 
- 
# Repeatedly read from the pipe, process each line. 
- 
while :; do
 
- 
    while read REPLY <&5; do
 
- 
- 
        $ifverbose -e “\nline: \”$REPLY\”“
 
- 
        for ((i=2; i<$#; i+=2)); do
 
- 
            pattern=${params[$i]}
 
- 
            command=${params[$((i+1))]}
 
- 
            $ifverbose “pattern: \”$pattern\”“
 
- 
            if [[ -z “${REPLY##*$pattern*}” ]]; then
 
- 
                $ifverbose “*MATCHED*”
 
- 
                $ifverbose “command: \”$command\”“
 
- 
                set +o noglob   # Allow pathname matching in commands
 
- 
                eval $command & 
- 
                set -o noglob   # No pathname expansion for patterns
 
- 
            fi
 
- 
        done
 
- 
    done
 
- 
- 
    # We get here whenever the pipe first blocks (returns EOF)
 
- 
    sleep 1               # Don’t loop too quickly
 
- 
done 
- 
- 
- 
### MAIN ROUTINE ENDS HERE ### 
- 
- 
- 
# Note: if we ever get here, the cleanup() function will be called 
- 
# automatically since we’re trapping on signal 0.