Skip to content

Instantly share code, notes, and snippets.

@mtekman
Last active December 15, 2021 23:24
Show Gist options
  • Save mtekman/9769fa3eb28dd0dbdd1e8ce802157e95 to your computer and use it in GitHub Desktop.
Save mtekman/9769fa3eb28dd0dbdd1e8ce802157e95 to your computer and use it in GitHub Desktop.
Determining the least used packages installed on your system
#!/usr/bin/bash
function pacman-last-used {
trap 'updateTermWidth' WINCH
storage_dir=${HOME}/.config/pacman-last
mkdir -p "$storage_dir"
unsorted=$storage_dir/"packages.1.exec_bins.log"
sorted=$storage_dir/"packages.2.exec_bins.sorted"
sorted_packs=$(mktemp) #"3.packages.sorted"
sorted_packs_flat=$storage_dir/"packages.3.first_last_usage"
sorted_packs_last=$storage_dir/"packages.4.last_usage_grouped_by_date"
cmd_package_ownership=""
cmd_package_name=""
function setPacmanFuncs {
## Checks for pacman and sets ownership functions
if [ "$(which pacman)" ]; then
cmd_package_ownership='pacman -Qo';
cmd_package_name='sed -r "s|^.+is owned by (.*)\s.+|\1|"';
else
echo "pacman could not be found" && exit 255;
fi
}
function updateTermWidth {
## Global term width, updated on SIGWINCH
width=$(stty size | cut -d" " -f 2)
}
function calc_packages {
## Gather and group last access times for all packages.
if [ -e "$unsorted" ] && [ -s "$unsorted" ]; then
echo -n " [Info] $unsorted already exists. Overwrite [y/n]? ";
read -r ans;
[ "$ans" != "y" ] && echo " [Info] Using existing." && return 0
fi
# Otherwise proceed
echo "" > "$unsorted"
echo " [Info] Checking:"
for dir in ${PATH//:/ };
do
! [ -e "$dir" ] && echo "[INFO] $dir does not exist, skipping" && continue
local num;
local count;
files="$(find "$dir" -maxdepth 1 -type f)"
num=$(echo "$files" | wc -l)
((count=0))
echo " $dir"
for bin in $files; do
((count++))
local full_path last_used owned_by line out
full_path=$bin ##$dir/$bin
last_used=$(stat -c %x "$full_path")
owned_by=$(eval "$cmd_package_ownership" "$full_path" 2>&1 | eval "$cmd_package_name")
[ "$(echo "$owned_by" | grep error)" != "" ] && owned_by="ERROR"
out="${last_used}\t${full_path}\t${owned_by}"
echo -e "$out" >> "$unsorted"
line=$( printf " (%4d / %4d) %30s -- %s" "$count" "$num" "$owned_by" "$full_path" )
printf "\r%-${width}s" "$line"
done
done
}
function sanityCheck {
file="$1"
message="$2"
if [[ -n "$file" ]]; then
! [ -e "$file" ] && echo "[Error] Cannot find $file. Terminating." && exit 255
fi
[ "$message" != "" ] && echo "$message"
}
setPacmanFuncs
echo ""
sanityCheck "" " - 1. Gathering info on all bins in PATH"
calc_packages
sanityCheck "$unsorted" " - 2. Sorting bins by package ownership and date"
awk '{print $1"\t"$4"\t"$5}' "$unsorted" | sort -k3 -k1n > "$sorted"
sanityCheck "$sorted" " - 3. Sorting packages by first and last usage"
awk -F'\t' '{print $3"\t"$1}' "$sorted" | uniq > "$sorted_packs"
# Flatten lines of adjacent packages
sanityCheck "$sorted_packs" ""
awk -F'\t' 's != $1 || NR ==1{s=$1;if(p){print p};p=$0;next}{sub($1,"",$0);p=p""$0;}END{print p}' "$sorted_packs"\
| awk -F"\t" '{print $1"\t"$2"\t"$NF}' > "$sorted_packs_flat"
sanityCheck "$sorted_packs_flat" " - 4. Grouping packages by date (last usage)"
sort -k3 "$sorted_packs_flat"\
| awk -F'\t' '{print $3"\t"$1}'\
| awk -F'\t' 's != $1 || NR ==1{s=$1;if(p){print p};p=$0;next}{sub($1,"",$1);p=p""$0;}END{print p}'\
> "$sorted_packs_last"
echo ""
echo " [Info] Files written:
-> $unsorted
-> $sorted
-> $sorted_packs_flat
-> $sorted_packs_last"
echo ""
}
pacman-last-used
@p00f
Copy link

p00f commented Aug 26, 2020

👍

@Strykar
Copy link

Strykar commented Aug 26, 2020

@mtekman Beautiful, even passes all linting checks! Nice work, I'm going to link this on the Arch wiki unless you wish to do it. Thank you.

@mtekman
Copy link
Author

mtekman commented Aug 26, 2020

@Strykar Please feel free!

@Strykar
Copy link

Strykar commented Aug 26, 2020

@mtekman Some food for thought.

Some of the suggestions made by others more knowledgeable in bash than I were about using:
-z and -n instead of '[ "$message" != "" ]'

Nested functions, which aren't global vs local functions, I think:
foo() { echo hello world; }; foo() { echo goodbye world; }; foo

"Not to mention that relatime is the default, so unless mtime changes, atime won't be updated.
Updating a package updates not just the mtime but also the atime. it means that package updates even for packages that you don't use will suggest that you regularly use the package. also, it depends on atime, which isn't a feature that everyone uses (e.g. you might mount with noatime)"

But it appears there's no real workaround to the atime issue, short of something that polls /proc/*/exe and that approach still doesn't work for installed libraries. Seems something in Linux's Audit framework or SELinux could be a solution.

@mtekman
Copy link
Author

mtekman commented Aug 26, 2020

Hmm I see. From the stat man page I don't see any real way to access ctime or another time parameter that is more permanent against package updates.

I guess this script is maybe more for highlighting packages that have both not been updated in a while and not been accessed in a while. It could be a good measure for rooting out stagnant packages perhaps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment