cachemoney is a robust and flexible package cache cleaner with a variety
of options. Much credit goes to DJ Mills and Pat Brisbin for the ideas
behind this script.
Signed-off-by: Dave Reisner
---
This sort of functionality has been in a few feature requests, and there exist
a number of tools that already provide this, but none flexible enough (or
portable enough) to be to my liking. This is all bash3 compat with awk, tested
on gawk 3.1.8, gawk 4.0.0, bwk (the one true), mawk, and busybox awk (which
fscking _sucks_) to do the heavy lifting (rather than bash4). It ends up being
insanely fast, with the drawback being that its dependent on the format of the
filename to find and consider packages for deletion. In other words, a valid
package tarball that isn't compliant with the extglob *.pkg.tar?(.+([^.]))
isn't going to be considered.
I'm a fan of the name, but I'm also happy to hear alternatives.
contrib/Makefile.am | 4 +
contrib/cachemoney.in | 294 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 298 insertions(+), 0 deletions(-)
create mode 100755 contrib/cachemoney.in
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index 69304a4..301de41 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -1,5 +1,6 @@
OURSCRIPTS = \
bacman \
+ cachemoney \
pacdiff \
paclist \
paclog-pkglist \
@@ -14,6 +15,7 @@ EXTRA_DIST = \
PKGBUILD.vim \
bacman.in \
bash_completion.in \
+ cachemoney.in \
paclog-pkglist.in \
pacdiff.in \
paclist.in \
@@ -29,6 +31,7 @@ MOSTLYCLEANFILES = $(OURSCRIPTS) $(OURFILES) *.tmp
edit = sed \
-e 's|@sysconfdir[@]|$(sysconfdir)|g' \
-e 's|@localstatedir[@]|$(localstatedir)|g' \
+ -e 's|@SIZECMD[@]|$(SIZECMD)|g' \
-e '1s|!/bin/bash|!$(BASH_SHELL)|g'
$(OURSCRIPTS): Makefile
@@ -50,6 +53,7 @@ all-am: $(OURSCRIPTS) $(OURFILES)
bacman: $(srcdir)/bacman.in
bash_completion: $(srcdir)/bash_completion.in
+cachemoney: $(srcdir)/cachemoney.in
pacdiff: $(srcdir)/pacdiff.in
paclist: $(srcdir)/paclist.in
paclog-pkglist: $(srcdir)/paclog-pkglist.in
diff --git a/contrib/cachemoney.in b/contrib/cachemoney.in
new file mode 100755
index 0000000..794ae45
--- /dev/null
+++ b/contrib/cachemoney.in
@@ -0,0 +1,294 @@
+#!/bin/bash
+#
+# cachemoney - clean yo pacman cache, dawg
+#
+# Copyright (C) 2011 Dave Reisner
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see http://www.gnu.org/licenses/.
+
+
+shopt -s extglob
+
+declare -a candidates=() cmdopts=() whitelist=() blacklist=()
+declare -i delete=0 diskspace=0 dryrun=0 filecount=0 keep=3 move=0 totalsaved=0
+declare cachedir=@localstatedir@/cache/pacman/pkg delim=$'\n' movedir= scanarch=
+
+msg() {
+ local mesg=$1; shift
+ printf "==> $mesg\n" "$@"
+} >&2
+
+error() {
+ local mesg=$1; shift
+ printf "==> ERROR: $mesg\n" "$@"
+} >&2
+
+die() {
+ error "$@"
+ exit 1
+}
+
+# reads a list of files on stdin and prints out deletion candidates
+pkgfilter() {
+ # there's whitelist and blacklist parameters passed to this
+ # script after the block of awk.
+
+ awk -v keep="$1" -v scanarch="$2" '
+ function parse_filename(filename, parts, count, i, pkgname, arch) {
+
+ count = split(filename, parts, "-")
+
+ i = 1
+ pkgname = parts[i++]
+ while (i <= count - 3) {
+ pkgname = pkgname "-" parts[i++]
+ }
+
+ arch = substr(parts[count], 1, index(parts[count], ".") - 1)
+
+ # filter on whitelist or blacklist
+ if (wlen && !whitelist[pkgname]) return
+ if (blen && blacklist[pkgname]) return
+
+ if ("" == packages[pkgname,arch]) {
+ packages[pkgname,arch] = filename
+ } else {
+ packages[pkgname,arch] = packages[pkgname,arch] SUBSEP filename
+ }
+ }
+
+ BEGIN {
+ # create whitelist
+ wlen = ARGV[1]; delete ARGV[1]
+ for (i = 2; i < 2 + wlen; i++) {
+ whitelist[ARGV[i]] = 1
+ delete ARGV[i]
+ }
+
+ # create blacklist
+ blen = ARGV[i]; delete ARGV[i]
+ while (i++ < ARGC) {
+ blacklist[ARGV[i]] = 1
+ delete ARGV[i]
+ }
+
+ # read package filenames
+ while (getline < "/dev/stdin") {
+ parse_filename($0)
+ }
+ }
+
+ END {
+ for (pkglist in packages) {
+ # idx[1,2] = idx[pkgname,arch]
+ split(pkglist, idx, SUBSEP)
+
+ # enforce architecture match if specified
+ if (!scanarch || scanarch == idx[2]) {
+ count = split(packages[idx[1], idx[2]], pkgs, SUBSEP)
+ if (count > keep) {
+ for(i = 1; i <= count - keep; i++) {
+ print pkgs[i]
+ }
+ }
+ }
+ }
+ }' "${@:3}"
+}
+
+size_to_human() {
+ awk -v size="$1" '
+ BEGIN {
+ suffix[1] = "KiB"
+ suffix[2] = "MiB"
+ suffix[3] = "GiB"
+ suffix[4] = "TiB"
+ count = 1
+
+ while (size > 1024) {
+ size /= 1024
+ count++
+ }
+
+ sizestr = sprintf("%.2f", size)
+ sub(/.?0+$/, "", sizestr)
+ printf("%s %s", sizestr, suffix[count])
+ }'
+}
+
+runcmd() {
+ if (( needsroot )); then
+ msg "Privilege escalation required"
+ if sudo -v &>/dev/null && sudo -l &>/dev/null; then
+ sudo "$@"
+ else
+ printf '%s ' 'root'
+ su -c "$(printf '%q ' "$@")"
+ fi
+ else
+ "$@"
+ fi
+}
+
+summarize() {
+ local -i filecount=$#
+ local seenarch= seen= arch= name=
+ local -r pkg_re='(.+)-[^-]+-[0-9]+-([^.]+)\.pkg.*'
+
+ if (( delete )); then
+ printf -v output 'finished: %d packages removed' "$filecount"
+ elif (( move )); then
+ printf -v output "finished: %d packages moved to \`%s'" "$filecount" "$movedir"
+ elif (( dryrun )); then
+ if (( verbose )); then
+ msg "Candidate packages:"
+ while read -r pkg; do
+ if (( verbose >= 3 )); then
+ [[ $pkg =~ $pkg_re ]] && name=${BASH_REMATCH[1]} arch=${BASH_REMATCH[2]}
+ if [[ -z $seen || $seenarch != $arch || $seen != $name ]]; then
+ printf '%s (%s):\n' "$name" "$arch"
+ fi
+ printf ' %s\n' "$pkg"
+ elif (( verbose >= 2 )); then
+ printf "$PWD/%s$delim" "$pkg"
+ else
+ printf "%s$delim" "$pkg"
+ fi
+ done < <(printf '%s\n' "$@" | sort -V)
+ printf '\n' >&2
+ fi
+
+ printf -v output 'finished dry run: %d candidates' "$filecount"
+ fi
+
+ if (( diskspace )); then
+ output+=" (diskspace saved: $(size_to_human "$totalsaved"))"
+ fi
+
+ msg "$output"
+}
+usage() {
+ cat <