script=`basename "$0"`
version='1.2.7'
VAR_LOG_DIR=/var/log
E_MISSING=64 E_BADARGS=65 E_BADDIR=66 E_NOTROOT=67 E_NOTCYGWIN=68 E_NOTGNU=69
case "`uname`" in
CYGWIN*)
under_cygwin=1
CYGWIN=${CYGWIN:-tty notitle glob nontsec}
CYGWIN+=" winsymlinks";;
*) under_cygwin=0;;
esac
function Ok {
[ -z "$*" ] || echo "$*"; exit 0
}
function Panic {
cat <<EOF >&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
function Version {
cat <<EOF >&2
$script version $version
EOF
}
function Usage {
Version; cat <<EOF >&2
Usage:
$script [-tgwiale] [-CLME] [-l NUM] [-f] [-n] [DIRNAME]
$script [-h | -V]
EOF
}
function Help {
Usage; cat <<EOF >&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 <http://www.visualco.de>.
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
find=`which find`
if ((!$under_cygwin)); then
res=`$find --version`
if [ "$?" -ne "0" ]; then
cat <<EOF >&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 <<EOF >&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 cat <<EOF >&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="\
-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"
DoFindImpl()
{
set -f || Panic 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
(($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}"
}
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
echo "Directory '$optdir'"
((optdry)) && \
echo "Dry run, nothing will be deleted"
((optverbose)) && [ "$UID" -eq "0" ] && \
echo "You are root"
if (($optcores)); then
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 $find "$optdir" -noleaf \
\( $taboo_expr \) -o \
\( $core_file_expr $remove_file_expr \) || Panic
else $find "$optdir" -noleaf \
\( $taboo_expr \) -o \
\( $core_file_expr -ok rm -v {} \; \) || Panic
fi
fi
fi
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
if ((!$under_cygwin)); then
echo " Finding dead symbolic links:"
$find "$optdir" \
\( $taboo_expr \) -o \
-type l -print | perl -nle '-e || print'
fi
fi
if ((optmaintainerclean))
then
echo "$what '.emacs.desktop.*':"
conditional_expr="\
-type f
( -name .emacs.desktop.*\
-o -name *.vcproj*.user \
-o -name *.vcxproj*.user )"
DoFindWithDotFiles
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
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 conditional_expr+=" -o -iregex ^.+/.*\\.(lock|log)$"
fi
else 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
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
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 conditional_expr+="\
-o -name *.*_original"
fi
conditional_expr="-type f ( $conditional_expr )"
DoFind
fi
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