#!/bin/bash ## chg -- for systematic revisions of the "replace A by B" sort -- by Eugene Reimer 1998-Feb; ## including character-substitutions and/or destuttering ala Unix tr; can also apply any Unix filter; always processes directories recursively; ## PREREQ: fullnameNOSLASH -- from http://ereimer.net/programs/ertools.zip ## ## Copyright © 1998,2009 Eugene Reimer; can be used, modified, copied, and distributed or sold under the terms of either the LGPL or the GPL (your choice); ## see http://www.gnu.org/licenses for the details of these terms. difterse () { diff -bBs -U0 "$@" |egrep -v '^(---|\+\+\+|@@) '; } ##show diffs in terse style cmdargs="$@" RC=1 while [ $# -gt 0 ] && [[ $1 == -* ]];do ##parse the options case "$1" in --sed) CMD="sed" ;; ##for chgsed; NOTE: sedscript need not follow immediately==!!== --tr) CMD="tr" ;; ##for chgtr -s|--des*) CMD="tr"; ARG="-s" ;; ##destutter, implies --tr -a|--fixhtma) CMD="tidy"; ARG="-omit -wrap 140 -b -ascii" ;; ##tidy options: -omit:omit optional end-tags; -b:cvt MS-chars; -win1252/-latin1/-ascii: charset -u|--fixhtmu) CMD="tidy"; ARG="-omit -wrap 140 -b -utf8" ;; ##tidy options: -omit:omit optional end-tags; -b:cvt MS-chars; -utf8: charset -c|--cmd) CMD=$2; shift; if [[ $CMD == *\ * ]];then ARG="${CMD#* }"; CMD="${CMD%% *}"; fi ;; ##07aug: ==!!may need fixups for strophs etc?? -k|--kee*) KEEPTS=1 ;; ##keep-timestamp option -n|--nob*) NOBKUP=1 ;; ##no-backup option -T|--tmp*) TMPBKUP=1 ;; ##backups under /tmp option -q|--qui*) QUIET=1; VERBOSE= ;; ##quiet option -t|--tes*) TEST=1; PROMPT=; QUIET= ;; ##test option -p|--pro*) PROMPT=1; TEST= ;; ##prompt option -m|--must*) MUST=1 ;; ##must-match option (user wants a message for "file-unchanged" case) -v|--verb*) VERBOSE=1; QUIET= ;; ##verbose output option (2010-04) -V|--vers*) CMD="vers"; VERS=1 ;; ##produce version info only -\?|--hel*) CMD="help"; HELP=0 ;; ##produce usage info only, exit-code=0 --) shift; break ;; ##ender for options esac shift; ##remove that arg done if [ "$CMD" = "" ];then ##expect A B positionals if [ $# -lt 3 ];then HELP=2; else CMD="sed"; BAR=$'\x01'; ARG="'s$BAR${1//\'/\\x27}$BAR${2//\'/\\x27}${BAR}g'"; shift; shift; fi elif [ "$CMD" = "sed" ] && [ "$ARG" = "" ];then if [ $# -lt 2 ];then HELP=2; else ARG="'${1//\'/\\x27}'"; shift; fi elif [ "$CMD" = "tr" ];then if [ $# -lt 3 ];then HELP=2; else ARG="$ARG '${1//\'/\\x27}' '${2//\'/\\x27}'"; shift;shift; fi fi ##[ $PROMPT$TEST ] || PROMPT=1; ##had PROMPT as default til recent changes trusted - soon became tiresome... [ $TEST ] && echo "---chg $cmdargs---" ##show the cmdline; 2007feb: $PROMPT now done per-file below; 2010-04: $VERBOSE also done per-file [ $VERS ] && echo "chg version:$(date -r$0 +%Y-%m-%d). Copyright 1998,2008 Eugene Reimer. Licensed under the GPL" && exit 0 ##echo "chg: CMD=$CMD; ARG=$ARG; HELP=$HELP; KEEPTS=$KEEPTS; NOBKUP=$NOBKUP; PROMPT=$PROMPT; QUIET=$QUIET; TEST=$TEST; TMPBKUP=$TMPBKUP;" ##DEBUG ##(note: ARG displays badly wrt the x01 chars) if [ $HELP ];then if [ $HELP -gt 0 ];then echo "CMD=$CMD; ARG=$ARG;"; fi echo "usage: chg options 'A' 'B' file-or-directory..." echo " or: chg options --sed sedpattern file-or-directory..." echo " or: chg options --tr set1 set2 file-or-directory..." echo " or: chg options --cmd filtercmd file-or-directory..." echo " or: chg options --fixhtma file-or-directory..." echo " or: chg options --fixhtmu file-or-directory..." echo "Revises files with s|A|B|g or sedpattern or tr-set1-set2-substitutions or filtercmd." echo " -s | --destutter implies --tr; removes stuttering repetition" echo " -k | --keeptimestamp revised file keeps its previous modification-timestamp" echo " -n | --nobackup no copy kept of previous file contents" echo " -T | --tmpbackup backup made under /tmp, instead of as tilde-file in current directory" echo " -q | --quiet suppress messages about file modification and backup" echo " -t | --test just show which files would be affected, but do not revise them" echo " -p | --prompt for each affected file, show diff's then prompt before revising it" echo " -? | --help just show this info on usage" echo " -v | --version just show version info" echo "For a directory, all subfiles are revised recursively." echo "Normally makes a backup copy when revising, but not for a file less than 5min old that has" echo " a backup copy (using the --keeptimestamp option can result in more frequent backups)." echo "See unchg if needing to undo." exit $HELP; ##exit fi ##[ $# -eq 0 ] && set ..?* .[^.]* *; ##used to match all files by default (now require at least one file on cmdline) chgfunc() { for f in "$@";do [ -L "$f" ] && continue [ -f "$f" ] || continue [[ $f == *~ ]] && continue ##skip a tilde-bkup file ##eval args "$CMD" "$ARG"; echo -n "continue?"; read ##DEBUG ##echo -n "CMD=$CMD; ARG=$ARG; f=$f; continue?"; read ##DEBUG ##note: need apostrophes around an s|x|y| arg, to prevent piping... eval "$CMD" "$ARG" <"$f" >/tmp/chg$$ ##execute sed or tr to make replacements, output into a tmp-file, for now; WHY USE eval==??== if [ $? -ne 0 ];then echo "chg: BAD RC=$? FROM $CMD"; exit; fi ##shouldmot happen; 06jun:DID w --fixhtma on USDAplantlist; removing exit was BAD idea w chgsed if ! cmp -s -- "$f" /tmp/chg$$;then ##--file was revised--!!-- TSOLD=$(date --reference="$f" +%s) ##timestamp of old file, in seconds since 1969 TSNEW=$(date --reference=/tmp/chg$$ +%s) ##timestamp of new file, in seconds since 1969 ((AGE=TSNEW-TSOLD)) ##nbr-seconds since previous revision to file chmod --reference="$f" /tmp/chg$$ ##preserve file-attributes, always ##chown --reference="$f" /tmp/chg$$ ##preserve file-ownership, always == YANKED because of spurious msgs in FC4 [ $KEEPTS ] && touch --reference="$f" /tmp/chg$$ ##preserve timestamp, only if --keep-timestamp specified fbk="$f~" ##for tilde-bkup in same dir [ $TMPBKUP ] && fbk=/tmp/bk-chg-$(fullnameNOSLASH "$f") ##for bkup under /tmp dir Changed="changed"; [ $TEST ] && Changed="would change"; ##need different messages for --test Quiet=$QUIET; Test=$TEST ##Test gets set if --test OR (--prompt AND user says NO); and serves to suppress file-replacement if [ $PROMPT ];then echo "---chg $cmdargs---" ##show the cmdline; 07feb: do here per-file if prompting ==NOTE: want $cmdopts... difterse -- "$f" /tmp/chg$$ ##show differences; when using expand / unexpand, better without -b on diff echo -n "replace $f -- Y/N?"; read; ##prompt, asking user whether or not to revise [[ $REPLY == [Nn]* ]] && Test=1 && Quiet=1 ##note: just CR means YES fi if [ $TEST ] || ! [ $Test ];then RC=0; fi ##supply return-code=zero if at least one file Changed - 07feb02 if [ $VERBOSE ] && ! [ $Test ];then echo; echo "---chg $cmdargs---" ##show the cmdline; ==NOTE: want $cmdopts rather than $cmdargs with potentially long list of filenames (2010-04) difterse -- "$f" /tmp/chg$$ ##show changes if $VERBOSE (2010-04) fi if [ $NOBKUP ];then [ $Test ] || /bin/mv -f -- /tmp/chg$$ "$f" ##replace file $f, without making backup-copy [ $Quiet ] || echo "$Changed $f" elif [ -e "$fbk" ] && ((AGE<=300));then ##note: decision based on age of file, not on age of backup-copy [ $Test ] || /bin/mv -f -- /tmp/chg$$ "$f" ##replace file $f, without making backup-copy [ $Quiet ] || echo "$Changed $f, without backup, as previous update $AGE seconds ago" else [ $Test ] || /bin/mv -f -- "$f" "$fbk" ##replace the backup-copy, since more than 5 minutes ago (or nonexistent) [ $Test ] || /bin/mv -f -- /tmp/chg$$ "$f" ##replace file $f [ $Quiet ] || echo "$Changed $f, with previous contents saved as $fbk" fi elif [ $MUST ];then echo "File $f was NOT changed by $cmdargs" ##sometimes want message for non-matching case (06dec) fi [ -e "/tmp/chg$$" ] && rm -f /tmp/chg$$ ##cleanup unneeded tmp-file done for f in "$@";do ##do directories recursively [ -L "$f" ] && continue [ -d "$f" ] || continue chgfunc "$f"/..?* "$f"/.[^.]* "$f"/* ##could be simplified using bash's GLOBIGNORE='.' done } chgfunc "$@" exit $RC 2005May: major revision, combining my chg, chgsed, chgsed-keeping-timestamp, chgtr scripts. wrote trivial chgsed, chgtr scripts to avoid having to kick old habits; 2006apr: new --fixhtm options, a replacement for my fixhtml-hi script, to convert non-ASCII chars to ampersand-entities. 2006jul: fixed the quirks with apostrophes in sed-patterns, I hope?? ==!!need similar change for sed-patterns that alter text containing backslash--??-- 2007Aug: added --cmd option, to get a general-purpose way to make any filter into a file-changing-with-backup-and/or-prompting command; eg: chg --cmd "expand" -- for a tabs-to-spaces reviser; eg: chg --cmd "sort -u" -- for a uniquifier ==WILL NOW SCRAP the --sed and --tr params; eg: chgsed will use --cmd instead; but need to resolve some param-passing issues... 2008-12-28: very weird bug: chgsed refusing to modify a file named webalizer_lang.h; ok on other files; ok on same file named tmp.h; WENT AWAY after reboot; 2010-04: added VERBOSE option; ==both its and PROMPT's per-file showing of cmdline-args would be better without ALL filenames, just current one==!!== added difterse function; using it for VERBOSE-output and for PROMPT-output;