Skip to content

Instantly share code, notes, and snippets.

@mtekman
Last active May 7, 2025 05:45
Show Gist options
  • Select an option

  • Save mtekman/9769fa3eb28dd0dbdd1e8ce802157e95 to your computer and use it in GitHub Desktop.

Select an option

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
@Strykar

Strykar commented Aug 25, 2020

Copy link
Copy Markdown

@mtekman

mtekman commented Aug 25, 2020

Copy link
Copy Markdown
Author

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

@gryffyn

gryffyn commented Aug 25, 2020

Copy link
Copy Markdown

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

mtekman commented Aug 25, 2020

Copy link
Copy Markdown
Author

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

gryffyn commented Aug 25, 2020

Copy link
Copy Markdown

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

mtekman commented Aug 25, 2020

Copy link
Copy Markdown
Author

👍 a quick which check would be a great solution

@dchinmay2

dchinmay2 commented Aug 25, 2020

Copy link
Copy Markdown

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

mtekman commented Aug 25, 2020

Copy link
Copy Markdown
Author

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

@mtekman

mtekman commented Aug 26, 2020

Copy link
Copy Markdown
Author

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

@dchinmay2

Copy link
Copy Markdown

👍

@Strykar

Strykar commented Aug 26, 2020

Copy link
Copy Markdown

@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

mtekman commented Aug 26, 2020

Copy link
Copy Markdown
Author

@Strykar Please feel free!

@Strykar

Strykar commented Aug 26, 2020

Copy link
Copy Markdown

@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

mtekman commented Aug 26, 2020

Copy link
Copy Markdown
Author

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