#!/bin/bash version="0.34" program=$(basename $0) NEW="" # If there are more than $NEW % new lines, skip update OLD="" # If there are more than $OLD % deleted lines, skip update FILE="" verbose="" silent="" # ---------------------------------------------------------------------- function usage() { cat <<EOF NAME $program - conditionally update target file. SYNOPSIS $program [OPTIONS] FILE DESCRIPTION $program reads input from a pipe or file and saves it to a target (FILE) if there are changes. If the new content is the same as the old, the target is left untouched. By default, the target is also left untouched if the new content is empty. There are options to also abstain from applying an update if the changes are too large, and to back up the previous version. The purpose is to handle files with dynamically generated content in such a manner that timestamps don't change if the content doesn't change, and mistakes in content generation doesn't unnecessarily propagate to the target. OPTIONS EOF if [ "$(uname)" = "Linux" ]; then egrep "^[ ]+[-][A-Za-z| -]+\*?\)[ ]+[A-Za-z].+#" $0 | tr -s "\t|" "\t," | sed -r -e 's/\)[ \t]+([A-Z]+)="\$2"[^#]*#/=\1\t/' -e 's/\)[^#]*#/\t/' else egrep "^[ ]+[-][A-Za-z| -]+\*?\)[ ]+[A-Za-z].+#" $0 | sed 's/\|.*"\$2"[^#]*#/ /'| sed -E 's/\|.*\)[^#]*#/ /' fi cat <<EOF AUTHOR Henrik Levkowetz <henrik@levkowetz.com> EOF exit } # ---------------------------------------------------------------------- function note() { if [ -n "$verbose" ]; then echo -e "$program: $*" fi } # ---------------------------------------------------------------------- function warn() { [ "$QUIET" ] || echo -e "$program: $*" } # ---------------------------------------------------------------------- function err() { echo -e "$program: $*" > /dev/stderr } # ----------------------------------------------------------------------------- function leave() { errcode=$1; shift if [ "$errcode" -ge "2" ]; then warn "$*"; else note "$*"; fi if [ -f "$tempfile" ]; then rm $tempfile; fi if [ -f "$difffile" ]; then rm $difffile; fi if [ "$errcode" = "1" -a "$RESULT" = "0" ]; then exit 0; else exit $errcode; fi } # ---------------------------------------------------------------------- # Set up error trap trap 'leave 127 "$program($LINENO): Command failed with error code $? while processing '$origfile'."' ERR # exit with a message if a command fails set -e # ---------------------------------------------------------------------- # Get any options # # Default values PAT="\$path\$base.%Y-%m-%d_%H%M" RESULT="0" QUIET="" # Based on the sample code in /usr/share/doc/util-linux/examples/parse.bash.gz if [ "$(uname)" = "Linux" ]; then GETOPT_RESULT=$(getopt -o bc:ef:hn:o:p:qrvV --long backup,maxchg:,empty,file:,help,maxnew:,maxold:,prefix:,report,quiet,verbose,version -n "$program" -- "$@") else GETOPT_RESULT=$(getopt bc:ef:hn:o:p:qrvV "$@") fi if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi note "GETOPT_RESULT: $GETOPT_RESULT" eval set -- "$GETOPT_RESULT" while true ; do case "$1" in -b|--backup) backup=1; shift ;; # Back up earlier versions by creating a backup file -c|--maxchg) CHG="$2"; shift 2 ;; # Limit on percentage of changed lines -e|--empty) empty=1; shift ;; # Permit the update to be empty (default: discard) -f|--file) FILE="$2"; shift 2 ;; # Read input from FILE instead of standard input -h|--help) usage; shift ;; # Show this text and exit -n|--maxnew) NEW="$2"; shift 2 ;; # Limit on percentage of new (added) lines -o|--maxold) OLD="$2"; shift 2 ;; # Limit on percentage of old (deleted) lines -p|--pat*) PAT="$2"; shift 2 ;; # Backup name base ('$path$base.%Y%m%d_%H%M') -q|--quiet) QUIET=1; shift;; # Be less verbose -r|--result) RESULT=1; shift ;; # Return 1 if update not done -v|--verbose) verbose=1; shift ;; # Be more verbose about what's happening -V|--version) echo -e "$program\t$version"; exit;; # Show version and exit --) shift ; break ;; *) echo "$program: Internal error, inconsistent option specification." ; exit 1 ;; esac done if [ $CHG ]; then OLD=$CHG; NEW=$CHG; fi if [ $# -lt 1 ]; then echo -e "$program: Missing output filename\n"; usage; fi origfile=$1 tempfile=$(mktemp) difffile=$(mktemp) if [ -e "$origfile" ]; then cp -p $origfile $tempfile # For ownership and permissions cat $FILE > $tempfile [ "$FILE" ] && touch -r $FILE $tempfile # This won't work if we don't have sufficient privileges: #chown --reference=$origfile $tempfile #chmod --reference=$origfile $tempfile else cat $FILE > $origfile [ "$FILE" ] && touch -r $FILE $tempfile leave 0 "Created file '$origfile'" fi origlen=$(wc -c < $origfile) newlen=$(wc -c < $tempfile) if [ $origlen = 0 -a $newlen = 0 ]; then rm $tempfile leave 1 "New content is identical (and void) - not updating '$origfile'." fi if [ $newlen = 0 -a -z "$empty" ]; then leave 1 "New content is void - not updating '$origfile'." fi diff $origfile $tempfile > $difffile || [ $? -le 1 ] && true # suppress the '1' error code on differences difflen=$(wc -l < $difffile) if [ $difflen = 0 ]; then leave 1 "New content is identical - not updating '$origfile'." fi if [ "$OLD" -o "$NEW" ]; then if [ "$NEW" ]; then maxnew=$(( $origlen * $NEW / 100 )); fi if [ "$OLD" ]; then maxdel=$(( $origlen * $OLD / 100 )); fi newcount=$(grep "^> " $difffile | wc -c) outcount=$(grep "^< " $difffile | wc -c) delcount=$(grep "^! " $difffile | wc -c) delcount=$(( $outcount + $delcount )) rm $difffile if [ "$OLD" ]; then if [ "$delcount" -ge "$maxdel" ]; then cp $tempfile $origfile.update leave 2 "New content has too many removed lines ($delcount/$origlen)\n - not updating '$origfile'.\nNew content placed in '$origfile.update' instead" fi fi if [ "$NEW" ]; then if [ "$newcount" -ge "$maxnew" ]; then cp $tempfile $origfile.update leave 2 "New content has too many added lines ($newcount/$origlen)\n - not updating '$origfile'.\nNew content placed in '$origfile.update' instead" fi fi fi if [ "$backup" ]; then path=${origfile%/*} name=${origfile##*/} base=${name%.*} ext=${origfile##*.} if [ "$ext" = "$origfile" ]; then ext="" elif [ ! "${ext%/*}" = "$ext" ]; then ext="" else ext=".$ext" fi if [ "$path" = "$origfile" ]; then path="" else path="$path/" fi ver=1 backfile=$(eval date +"$PAT") backpath="${backfile%/*}" if [ "$backpath" = "$backfile" ]; then backpath="." fi if [ ! -d $backpath ]; then if [ -e $backpath ]; then leave 3 "The backup path '$backpath' exists but isn't a directory" else mkdir -p $backpath fi fi while [ -e "$backfile,$ver$ext" ]; do ver=$(( $ver+1 )) done note "Saving backup: $backfile,$ver$ext" cp -p "$origfile" "$backfile,$ver$ext" chmod -w "$backfile,$ver$ext" || true fi if ! mv $tempfile $origfile; then cp -p $tempfile $origfile; fi leave 0 "Updated file '$origfile'"