#!/bin/bash # # cleanup -- Maintenance tool to remove unneeded files and directories # # Please see usage information in the HERE document defined below. # ######################### # $Author: Andreas Spindler$ # $Writestamp: 2011-06-25 22:40:32$ # $Maintained at: http://www.visualco.de$ ######################################################################## # Portability / Environment script=`basename "$0"` version='1.2.7' VAR_LOG_DIR=/var/log E_MISSING=64 # Missing programs E_BADARGS=65 # Bad argument format E_BADDIR=66 # Can't change directory E_NOTROOT=67 # Non-root exit error E_NOTCYGWIN=68 # Non-Cygwin-shell E_NOTGNU=69 # GNU utility required case "`uname`" in CYGWIN*) under_cygwin=1 CYGWIN=${CYGWIN:-tty notitle glob nontsec} CYGWIN+=" winsymlinks";; *) under_cygwin=0;; esac # `Ok' is the normal exit function (success). # `Panic' function signals runtime errors to the shell. # `Croak' functions signal logical errors or warnings. function Ok { [ -z "$*" ] || echo "$*"; exit 0 } function Panic { cat <&2 Panic! in shell $$, pipe $?, terminal `tty`: $* $script exits with -1 EOF exit -1 } function CroakFind { ((optverbose)) && echo "WARNING: $find exited with code $?" } >&2 function CroakDir { echo "$*: No such directory"; exit $E_BADDIR } >&2 function CroakArgs { Usage; exit $E_BADARGS } function CroakUnlessCygwin { if ((!$under_cygwin)); then echo "WARNING: Not a Cygwin system. Use '-f' to force deletion of Windows files" exit $E_NOTCYGWIN fi } >&2 function CroakUnlessGnuFind { if ((!$have_gnu_find)); then echo "WARNING: GNU find required" exit $E_NOTGNU fi } >&2 function CroakUnlessRoot { if [ "$UID" -ne "0" ]; then echo "WARNING: You must be root to do that." exit $E_NOTROOT fi } >&2 ######################################################################## # Commandline options # # The script will run in dry mode unless one of the following options are # given: # -a -A -e -E -t -g -i -w -C -M function Version { cat <&2 $script version $version EOF } function Usage { Version; cat <&2 Usage: $script [-tgwiale] [-CLME] [-l NUM] [-f] [-n] [DIRNAME] $script [-h | -V] EOF } function Help { Usage; cat <&2 Description: Efficiently and safely find and/or remove garbage files, compiler intermediate files, suspicious files, "core" files, empty files/directories and symbolic links that point to nothing. Handles dot files/directories and source repositories (e.g. ".svn") carefully. Works nicely on Windows systems under Cygwin. Nothing is actually removed unless one or more of the following options is explicitly specified: -t -g -w -i -e -E -a -A -M -C -L Options: -n Dry run: do not actually remove any files; just print filenames (default). -f Force harder cleaning/release some constraints. -V Print version information. -v Enable verbose mode. -h, -H Print this information. Remove Options: -t Remove temporary files: *~ #*# .tmp .temp .cache NUL TAGS More precious temporary files require -f: .log .lock -g Remove intermediate files left behind by GNU tools: .d .o .gch a.out More precious files require -f: .elc -w Remove intermediate files left behind by Microsoft's Visual Studio and Windows programs: .tlog .dep .idb .suo .ilk .rsp .ncb .sbr .pch .bsc .clw .obj .aps .exp .tlb .crf .sdf .intermediate.manifest BuildLog.htm MSVC.BND .unsuccessfulbuild .lastbuildstate More precious Visual Studio files require -f: .opt .pdb .map .res Remove files left behind by Cygwin tools: .exe.stackdump -i Remove files left behind by image processors: pspbrwse.jbf thumbs.db ExifBrowser.Thumbnails Exiftool backup files require -f: .*_original -e, -0 Remove empty files (zero file size). -E Remove empty directories. -a Short for "-tigw". -A Like before, plus empty files/directories; short for "-tigweE". -l List files only; short for "-An". Special Cleaning Options: -M Maintainer clean: .emacs.desktop.* *.vcproj.*.user *.vcxproj.user More precious files/directories require -f. WARNING: -Mf unties CVS/Subversion/SourceSafe working copies: .svn/ .cvs/ vssver2.scc mssccprj.scc *.vspscc -C System maintenance. Remove all "core"-files which are at least 5 days old, after asking the user for permission on each file (or use -f). Only the root user can do this. Furthermore find and print suspicious files (THESE ARE NEVER REMOVED): - World writable files. - Files with no valid owner and/or group. - SetUID files. - Files with unusual permissions, sizes, names, dates. - Symbolic links that point to nothing. -L NUM Remove "$VAR_LOG_DIR/wtmp" and tailor "$VAR_LOG_DIR/messages" to NUM lines (minium 100 or use -f to allow NUM to be less than 100 lines). Requires the root user. Examples: Print a list of removable files: \$ $script As before, but additionally with empty files/directories: \$ $script -l As before, but print all removable files, i.e. including those that one might not discard thoughlessly: \$ $script -lf Print empty files and directories \$ $script -eEn Untie CVS/Subversion/SourceSafe working copy: \$ $script -Mf Installation/Prerequisites: Portable shell script. Works on any UN*X and Windows (Cygwin) system. Copy this script into your path (e.g. to "/usr/local/bin" or "\$HOME/bin"). Requires uname, getopts, sh and find. Maintained at . Exit codes: 0 Indicates success to the shell. -1 Indicates an unexpected error. $E_BADARGS Bad command-line arguments. $E_BADDIR "-l" could not change to "$VAR_LOG_DIR". $E_NOTROOT Root user required to perform. $E_NOTCYGWIN "-w" requires a Cygwin-driven shell (use "-f") $E_NOTGNU GNU utility required EOF } optdry=1 optforcedry=0 optverbose=0 optforce=0 opttemp=0 optgnujunk=0 optemptyfiles=0 optemptydirs=0 optmsjunk=0 optpixeljunk=0 optcores=0 optloglines=0 optmaintainerclean=0 optdir= LoadDefaultOpts() { optdry=1 opttemp=1 optgnujunk=1 optpixeljunk=1 ((under_cygwin)) && optmsjunk=1 } LoadMoreOpts() { LoadDefaultOpts optemptyfiles=1 optemptydirs=1 } while getopts 'aAl0eEtigwCL:MfnvhHV' opt do case $opt in a) LoadDefaultOpts; optdry=0;; A) LoadMoreOpts; optdry=0;; l) LoadMoreOpts; optforcedry=1;; 0) optemptyfiles=1; optdry=0;; e) optemptyfiles=1; optdry=0;; E) optemptydirs=1; optdry=0;; t) opttemp=1; optdry=0;; g) optgnujunk=1; optdry=0;; i) optpixeljunk=1; optdry=0;; w) optmsjunk=1; optdry=0;; C) optcores=1; optdry=0;; M) optmaintainerclean=1; optdry=0;; L) optloglines=$OPTARG; optdry=0;; f) optforce=1;; n) optforcedry=1;; v) optverbose=1;; V) Version; Ok;; [hH]) Help; Ok;; *) CroakArgs;; esac done shift $(($OPTIND - 1)) optdir=${1:-.} if [ -n "$2" ]; then echo "$script: Only one search directory allowed" >&2 exit $E_BADDIR fi (($optemptyfiles + $optemptydirs + $opttemp + $optgnujunk + $optpixeljunk + $optmsjunk + \ $optcores + $optmaintainerclean + $optloglines)) || \ LoadDefaultOpts (($optforcedry)) && optdry=1 (($under_cygwin)) && \ optdir=`cygpath "$optdir"` [ "$optdir" != "/" ] || (($under_cygwin)) || \ CroakUnlessRoot [ -d "$optdir" ] || \ CroakDir "$optdir" (($optmsjunk && !$optforce)) && \ CroakUnlessCygwin # -w requires -f when not under Cygwin ######################################################################## # Test and run find # # Get find, test whether '-regextpye' works. A few Windows tools, such as # find.exe, link.exe and sort.exe, may conflict with the Cygwin versions. Try # the full path /usr/bin/find then. find=`which find` if ((!$under_cygwin)); then res=`$find --version` if [ "$?" -ne "0" ]; then cat <&2 WARNING: find is '$find', the native Windows find utility. Possibly the Cygwin-bin-directory does not come first in PATH (see "~/.bash_login" and "/etc/profile"). EOF find=/usr/bin/find res=`$find --version` if [ "$?" -ne "0" ]; then Panic "find not found" else cat <&2 WARNING: Using '$find' explicitly. EOF exit $E_NOTCYGWIN fi fi fi res=`$find -regextype posix-egrep 2>&1 &>/dev/null` if [ "$?" -ne "0" ]; then # some prehistoric find version? cat <&2; WARNING: '$find' is not GNU find, since it does not understand '-regextype' EOF have_gnu_find=0 else have_gnu_find=1 fi if (($have_gnu_find)); then regex_expr='-regextype posix-egrep' remove_file_expr='-exec rm -fv {} +' else regex_expr='' remove_file_expr='-exec rm -fv {} ;' fi remove_dir_expr='-exec rmdir -v {} ;' if (($optdry)); then what=" Finding" execute_expr="-print" else what=" Removing" execute_expr="$remove_file_expr" fi # taboo_expr prunes some root directories. Assume "/windows-X-drive" is the # Windows partition on a dual-boot computer. taboo_expr="\ -path /proc -prune \ -o -path /sys -prune \ -o -path /dev -prune \ -o -iwholename /windows-*-drive -prune" (($under_cygwin)) && taboo_expr+="\ -o -iwholename /cygdrive/[a-z]/System?Volume?Information -prune -o -path /cygdrive/[a-z]/\$RECYCLE.BIN/* -prune -o -path /cygdrive -prune" # Base function running find. DoFindImpl() { set -f || Panic # disable file pattern expansion; note that the '~' # filename metacharacter is also disabled by this option local dotfiles=${1:-0} dotdirs=${2:-0} exec="$3" if (($dotfiles)); then if (($dotdirs)); then if (($optverbose)); then echo "WARNING: finding file- and directory names beginning with a dot" >&2 set -x fi $find "$optdir" $find_opts $regex_expr \ \( $taboo_expr \) -o \ \( \( $conditional_expr \) -a \ \( $exec \) \) || CroakFind else if (($optverbose)); then echo "WARNING: finding filenames beginning with a dot (but no dot-directories)" >&2 set -x fi $find "$optdir" $find_opts $regex_expr \ \( $taboo_expr \) -o \ \( -type d -path '*/.*' -prune \) -o \ \( \( $conditional_expr \) -a \ \( $exec \) \) || CroakFind fi else if (($dotdirs)); then if (($optverbose)); then echo "WARNING: finding directory names beginning with a dot (but no dot-files)" >&2 set -x fi $find "$optdir" $find_opts $regex_expr \ \( $taboo_expr \) -o \ \( -type f -name '.*' \) -o \ \( \( $conditional_expr \) -a \ \( $exec \) \) || CroakFind else # Find no dot files and no directories (default). (($optverbose)) && set -x $find "$optdir" $find_opts $regex_expr \ \( $taboo_expr \) -o \ \( -path '*/.*' \) -o \ \( \( $conditional_expr \) -a \ \( $exec \) \) || CroakFind fi fi set +xf } DoFind() { DoFindImpl 0 0 "${1:-$execute_expr}" } DoFindWithDotDirs() { DoFindImpl 0 1 "${1:-$execute_expr}" } DoFindWithDotFiles() { DoFindImpl 1 0 "${1:-$execute_expr}" } DoFindWithDots() { DoFindImpl 1 1 "${1:-$execute_expr}" } ######################################################################## # (0) System files. Truncate $VAR_LOG_DIR/messages to $optloglines (root only). # if ((${optloglines:-0})); then if ((!$optforce)); then [ $optloglines -ge 100 ] || optloglines=100 fi if [ -f "$VAR_LOG_DIR/messages" ]; then if (($optdry)); then echo "'$VAR_LOG_DIR/messages' lines: $(wc -l \"$VAR_LOG_DIR/messages\")" echo "'$VAR_LOG_DIR/messages' words: $(wc -m \"$VAR_LOG_DIR/messages\")" else echo " Truncating '$VAR_LOG_DIR/messages' to $optloglines lines" CroakUnlessRoot tail -n $optloglines "$VAR_LOG_DIR/messages" > "$VAR_LOG_DIR/messages.cleanup" || Panic mv -f "$VAR_LOG_DIR/messages.cleanup" "$VAR_LOG_DIR/messages" fi else echo "WARNING: '$VAR_LOG_DIR/messages' not found" >&2 fi if [ -e "$VAR_LOG_DIR/wtmp" ]; then if ((optdry)); then echo "'$VAR_LOG_DIR/wtmp' lines: $(wc -l \"$VAR_LOG_DIR/wtmp\")" echo "'$VAR_LOG_DIR/wtmp' words: $(wc -m \"$VAR_LOG_DIR/wtmp\")" else echo " Truncating '$VAR_LOG_DIR/wtmp'" CroakUnlessRoot cat /dev/null > wtmp fi else echo "WARNING: '$VAR_LOG_DIR/wtmp' not found" >&2 fi Ok fi ######################################################################## # (1) Print suspicious files, remove "core" files. # echo "Directory '$optdir'" ((optdry)) && \ echo "Dry run, nothing will be deleted" ((optverbose)) && [ "$UID" -eq "0" ] && \ echo "You are root" if (($optcores)); then # Find core files. Without -n remove the file; use -f (force) to skip # asking the user for permission. The user must be root. echo "$what 'core' files:" CroakUnlessGnuFind core_file_expr="-atime -5 -type f -name core" if (($optdry)); then $find "$optdir" -noleaf \ \( $taboo_expr \) -o \ \( $core_file_expr -print \) || Panic else if [ "$UID" -ne "0" ]; then echo "WARNING: You must be root to remove core files" >&2 else if ((optforce)); then # do not ask $find "$optdir" -noleaf \ \( $taboo_expr \) -o \ \( $core_file_expr $remove_file_expr \) || Panic else # ask for user permission $find "$optdir" -noleaf \ \( $taboo_expr \) -o \ \( $core_file_expr -ok rm -v {} \; \) || Panic fi fi fi # Print suspicious files (NOT REMOVED). # # We're using a well-known find expression here. World-writebale (-perm 2), # no symlinks, no sockets and no directories with the sticky/text bit set # (symlinks, sockets and directories with the sticky bit set are often # world-writable and generally not suspicious.) -noleaf is required for # filesystems of mounted CD drives. if ((!$under_cygwin)); then echo " Finding suspicious files:" CroakUnlessGnuFind $find "$optdir" -noleaf \ \( $taboo_expr \) -o \ \( -perm -2 ! -type l ! -type s ! \( -type d -perm -1000 \) \) -print fi # Find symbolic links that point to nothing (NOT REMOVED). # # This is a tip from the Unix Guru Universe (http://www.ugu.com). To find # dead symbolic links we let perl determine all links that point to # nothing. We may further pipe through "rm -vf" to actually delete these # links. if ((!$under_cygwin)); then echo " Finding dead symbolic links:" $find "$optdir" \ \( $taboo_expr \) -o \ -type l -print | perl -nle '-e || print' fi fi ######################################################################## # (2) Maintainer clean # if ((optmaintainerclean)) then # Find/remove ".emacs.desktop.*", ".svn/*", ".cvs/*" etc. echo "$what '.emacs.desktop.*':" conditional_expr="\ -type f ( -name .emacs.desktop.*\ -o -name *.vcproj*.user \ -o -name *.vcxproj*.user )" DoFindWithDotFiles # With -f find or remove ".svn/*". # WARNING: -Mf unties a Subversion/SourceSafe working copy. if (($optforce)); then echo "$what Subversion/CVS/SourceSafe files:" find_opts="-depth" conditional_expr="\ ( -type d ( -path */.svn -o -path */.cvs ) )\ -o ( -type f ( -name vssver2.scc -o -name mssccprj.scc -o -name *.vspscc ) )" if (($optdry)); then DoFindWithDots else DoFindWithDots "-exec rm -frv {} ;" fi find_opts= fi fi ######################################################################## # (3) Remove temporary/intermediate/garbage files. # # When find does not honor "posix-egrep" use glob patterns. if (($opttemp)); then echo "$what temporary files:" if (($have_gnu_find)); then conditional_expr+="\ -regex ^.+/(#.*#|.*~)$ \ -o -iregex ^.+/(nul|tags)$ \ -o -iregex ^.+/.*\\.(tmp|temp|cache)$" if (($optforce)); then # remove harder: *.lock, *.log conditional_expr+=" -o -iregex ^.+/.*\\.(lock|log)$" fi else # no -regextpye (slower) conditional_expr+="\ -name #*# -o -name *~ \ -o -iname *.cache \ -o -iname nul -o -iname tags \ -o -iname *.tmp -o -iname *.temp" if (($optforce)); then conditional_expr+="\ -o -name *.log -o -name *.lock" fi fi # Allow dot-files to find (e.g. ".log") but prune dot-directories. conditional_expr="-type f ( $conditional_expr )" DoFindWithDotFiles fi if (($optgnujunk)); then echo "$what GNU/gcc removable files:" if (($have_gnu_find)); then conditional_expr="\ -name a.out \ -o -regex ^.+/.*\\.(d|o|gch)$" else conditional_expr="\ -name a.out \ -o -name *.d -o -name *.o -o -name *.gch" fi if (($optforce)); then conditional_expr+="\ -o -name *.elc" fi conditional_expr="-type f ( $conditional_expr )" DoFind fi if (($optmsjunk)); then # See also "Common File Extensions Used by Visual C++", # http://support.microsoft.com/kb/132340/EN-US/ echo "$what Microsoft/Cygwin removable files:" if (($have_gnu_find)); then conditional_expr="\ -name *.exe.stackdump \ -o -name BuildLog.htm -o -name MSVC.BND \ -o -name *.intermediate.manifest \ -o -name *.lastbuildstate -o -name *.unsuccessfulbuild \ -o -iregex ^.+/.*\\.(obj|rsp|dep|tlb|tlog|aps|exp)$ \ -o -iregex ^.+/.*\\.(sdf|ncb|clw|cpl|crf|suo|sbr|ilk|pch|mdp|idb|pg[dc]|bsc)$" else conditional_expr="\ -name *.exe.stackdump \ -o -name BuildLog.htm -o -iname MSVC.BND \ -o -name *.intermediate.manifest \ -o -name *.lastbuildstate -o -name *.unsuccessfulbuild \ -o -name *.tlog \ -o -name *.obj -o -name *.res -o -name *.clw -o -name *.cpl \ -o -name *.rsp -o -name *.ncb -o -name *.suo -o -name *.sbr \ -o -name *.ilk -o -name *.pch -o -name *.tlb -o -name *.idb \ -o -name *.bsc -o -name *.crf -o -name *.mdp -o -name *.aps \ -o -name *.exp -o -name *.pgd -o -name *.pgc -o -name *.sdf" fi if (($optforce)); then conditional_expr+="\ -o -iname *.opt -o -iname *.res \ -o -iname *.map -o -iname *.pdb" fi conditional_expr="-type f ( $conditional_expr )" DoFind fi if (($optpixeljunk)); then echo "$what image thumbnail removable files:" conditional_expr="\ -iname pspbrwse.jbf \ -o -iname ExifBrowser.Thumbnails \ -o -iname thumbs.db" if (($optforce)); then # remove harder: exiftool backup files conditional_expr+="\ -o -name *.*_original" fi conditional_expr="-type f ( $conditional_expr )" DoFind fi ######################################################################## # (4) Remove empty files and directories. # if (($optemptyfiles)); then echo "$what empty files:" conditional_expr="-type f -empty" DoFind fi if (($optemptydirs)); then echo "$what empty directories:" find_opts="-depth" conditional_expr="-type d -empty" if (($optdry)); then DoFind else DoFind "$remove_dir_expr" fi find_opts= fi ((optdry)) && ((optverbose)) && \ echo "Dry run, nothing was deleted" Ok # Local Variables: # coding: iso-8859-1-unix # fill-column: 79 # End: # cleanup ends here