#!/usr/bin/bash # # paccache - flexible pacman cache cleaning # # 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 . # Expand to nothing if there are no matches shopt -s nullglob shopt -s extglob declare -r myname='paccache' declare -r myver='1.3.0' LIBRARY=${LIBRARY:-'/usr/share/makepkg'} declare -a cachedirs=() candidates=() cmdopts=() whitelist=() blacklist=() declare -i delete=0 dryrun=0 filecount=0 move=0 totalsaved=0 verbose=0 declare -i min_atime=0 min_mtime=0 declare delim=$'\n' keep=3 movedir= scanarch= QUIET=0 USE_COLOR='y' # Import libmakepkg source "$LIBRARY"/util/message.sh source "$LIBRARY"/util/parseopts.sh 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" -v min_atime="$3" -v min_mtime="$4" ' function basename(str) { sub(".*/", "", str); return str; } function parse_filename(filename, atime, mtime, parts, count, i, pkgname, arch) { if (0 + min_atime + min_mtime != 0) { # atime and mtime are in the first two columns and the # separator is a single space split(filename, parts, " ") atime = parts[1] mtime = parts[2] filename = substr(filename, length(atime) + length(mtime) + 3) } count = split(basename(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 atimes[pkgname,arch] = atime mtimes[pkgname,arch] = mtime } else { packages[pkgname,arch] = packages[pkgname,arch] SUBSEP filename atimes[pkgname,arch] = atimes[pkgname,arch] SUBSEP atime mtimes[pkgname,arch] = mtimes[pkgname,arch] SUBSEP mtime } } 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) } 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) split(atimes[idx[1], idx[2]], atime, SUBSEP) split(mtimes[idx[1], idx[2]], mtime, SUBSEP) for(i = 1; i <= count - keep; i++) { # If checking file age, potentially keep more candidates if ((0 + min_atime == 0 || (strtonum(atime[i]) < 0 + min_atime)) && (0 + min_mtime == 0 || (strtonum(mtime[i]) < 0 + min_mtime)) \ ) { print pkgs[i] } } } } }' "${@:5}" } size_to_human() { awk -v size="$1" ' BEGIN { suffix[1] = "B" suffix[2] = "KiB" suffix[3] = "MiB" suffix[4] = "GiB" suffix[5] = "TiB" suffix[6] = "PiB" suffix[7] = "EiB" count = 1 while (size > 1024) { size /= 1024 count++ } sizestr = sprintf("%.2f", size) sub(/\.?0+$/, "", sizestr) printf("%s %s", sizestr, suffix[count]) }' } runcmd() { "$@" } summarize() { (( QUIET )) && return local -i filecount=$1; shift 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 seen=$name seenarch=$arch printf '%s (%s):\n' "${name##*/}" "$arch" fi printf ' %s\n' "${pkg##*/}" elif (( verbose >= 2 )); then printf "%s$delim" "$pkg" else printf "%s$delim" "${pkg##*/}" fi done < <(printf '%s\n' "$@" | pacsort --files) fi printf -v output 'finished dry run: %d candidates' "$filecount" fi echo msg "$output (disk space saved: %s)" "$(size_to_human "$totalsaved")" } usage() { cat < [options] [targets...] Operations: -d, --dryrun perform a dry run, only finding candidate packages. -m, --move move candidate packages to "dir". -r, --remove remove candidate packages. Options: --min-atime