Skip to content

Instantly share code, notes, and snippets.

@mtekman
Last active December 15, 2021 23:24
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • 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 25, 2020

Screenshot from 2020-08-25 17-28-14

There's an error on line 117

@Strykar
Copy link

Strykar commented Aug 25, 2020

@mtekman Might be worth running this through shellcheck.net, I did, and ran the fixes but there seem to be a bunch more that pop up even after, like your choice of exit -1 and local etc. which may just be semantics.

@mtekman
Copy link
Author

mtekman commented Aug 25, 2020

Thanks, linting is definitely not one of my strong suits -- if you want to make a PR against this I'd be happy to check it.
Otherwise I'll fix this later this afternoon

@Strykar
Copy link

Strykar commented Aug 25, 2020

@mtekman
Copy link
Author

mtekman commented Aug 25, 2020

the SC2155, SC2242 seem like easy fixes, the others I will have to play with -- thanks for the revision!

@gryffyn
Copy link

gryffyn commented Aug 25, 2020

The OS check just checks if you have the Arch kernel. This won't work without modification on Arch/Manjaro running anything but the stock Arch kernel.

@mtekman
Copy link
Author

mtekman commented Aug 25, 2020

I've actually changed that line 100 times, because it's not even that stable on Arch :D

Do you know of a more robust way to extract the OS? Or should we just trash the OS check and assume people will be using Arch?

@gryffyn
Copy link

gryffyn commented Aug 25, 2020

Could just look in /etc/lsb-release, but that can be modified. Could even just check for the presence of the /usr/bin/pacman binary

@mtekman
Copy link
Author

mtekman commented Aug 25, 2020

👍 a quick which check would be a great solution

@p00f
Copy link

p00f commented Aug 25, 2020

just check if pacman is available by trying pacman --version or which pacman . If there is no error, this script should work on all pacman distros

@mtekman
Copy link
Author

mtekman commented Aug 25, 2020

updated and completely untested :-)
my home machine is not arch, I'll test tomorrow

@mtekman
Copy link
Author

mtekman commented Aug 26, 2020

@Strykar @gryffyn @p00f -- just updated and tested, please let me know that it works for you too

@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