Search code examples
bashperlhomebrew

How do I find the oldest used files from a program that links its bins, like Homebrew?


How do I find the oldest used files from this?

MBP:~ user$ \ls -la /usr/local/bin/ | egrep -i "Cellar|Cask" | head
lrwxr-xr-x    1 user  admin       37 Sep 13 12:19 2to3 -> ../Cellar/[email protected]/3.11.5/bin/2to3
lrwxr-xr-x    1 user  admin       43 Sep 13 12:26 2to3-3.10 -> ../Cellar/[email protected]/3.10.13/bin/2to3-3.10
lrwxr-xr-x    1 user  admin       42 Sep 13 12:19 2to3-3.11 -> ../Cellar/[email protected]/3.11.5/bin/2to3-3.11
lrwxr-xr-x    1 user  admin       40 Sep 13 12:35 2to3-3.9 -> ../Cellar/[email protected]/3.9.18/bin/2to3-3.9
lrwxr-xr-x    1 user  admin       37 Aug 26 14:25 4channels -> ../Cellar/libraw/0.21.1/bin/4channels
lrwxr-xr-x    1 user  admin       52 Sep 13 12:34 Magick++-config -> ../Cellar/imagemagick/7.1.1-15_1/bin/Magick++-config
lrwxr-xr-x    1 user  admin       54 Sep 13 12:34 MagickCore-config -> ../Cellar/imagemagick/7.1.1-15_1/bin/MagickCore-config
lrwxr-xr-x    1 user  admin       54 Sep 13 12:34 MagickWand-config -> ../Cellar/imagemagick/7.1.1-15_1/bin/MagickWand-config
lrwxr-xr-x    1 user  admin       36 Jun  4  2023 acountry -> ../Cellar/c-ares/1.19.1/bin/acountry
lrwxr-xr-x    1 user  admin       32 Jun  4  2023 adig -> ../Cellar/c-ares/1.19.1/bin/adig
MBP:~ user$

If anyone uses Homebrew on Mac, I'm looking to see if I can or should clean up anything. I have a ton of installed apps, and a brew update takes forever. I need to find which apps I stopped using.


Solution

  • Links in the input directory sorted by the oldest-used, per resolved symlinks

    use File::Spec;
    
    my $dir = '/usr/local/bin';
    
    my @sorted_by_oldest_used = 
        map { $_->[0] }
        sort { $b->[1] <=> $a->[1] }
        map { 
            my $t = readlink;
            $t = "$dir/$t" if not File::Spec->file_name_is_absolute($t); 
            $t =~ /Cellar|Cask/ ? [ $_, -A $t ] : ()
        }   
        grep { -l }     # keep only links
        glob "$dir/*";
    

    It tests whether the target name is absolute because readlink may return the name with only the path relative to the symlink directory, or the full path.

    Please let me know if I need to explain this code.

    The glob pulls everything that is matched by the wildcard-ed expression. If you need to be more precise about the entries add to that grep filter and see filetest (-X) for different flags.

    Include this code in your programs as suitable. For a demo, here is a command-line version

    perl -MFile::Spec -wE'
        $d = shift // q(/usr/local/bin); 
        say for
            map { $_->[0] }
            sort { $b->[1] <=> $a->[1] }
            map {
                my $t = readlink;
                $t = "$d/$t" if not File::Spec->file_name_is_absolute($t);
                $t =~ /Cellar|Cask/ ? [ $_, -A $t ] : ()
            }
            grep { -l }
            glob "$d/*"'
    

    This can be copy-pasted into a terminal and ran, in most shells.

    Or, if there is no need for the path test this simplifies a little. Then let's also keep more info

    perl -MData::Dumper -wE'
        $d = shift // q(/usr/local/bin); 
        say Dumper
            sort { $b->[2] <=> $a->[2] }
            map { (-l and ($t = readlink) =~ /Cellar|Cask/) ? [ $_, $t, -A $t ] : () }
            glob "$d/*"'
    

    This uses /usr/local/bin by default, unless a directory is given on the command line at invocation, as perl -wE'...' dirname.

    I don't use Homebrew on a Mac so let me know if something's off in relation to that. Thanks to tripleee for comments.


    Or, keep more information from processing for a more sensible analysis

    use File::Spec;
    
    use Data::Dumper;
    
    my @sorted_by_oldest_used = 
        sort { $b->[2] <=> $a->[2] }
        map { 
            my $t = readlink;
            $t = "$dir/$t" if not File::Spec->file_name_is_absolute($t); 
            $t =~ /Cellar|Cask/ ? [ $_, $t, -A $t ] : ()
        }   
        grep { -l }
        glob "$dir/*";
    
    say Dumper @sorted_by_oldest_used;
    

    The final array, that is then printed, now has arrayrefs for elements, each with link name, target name, and the number of days since last access (-A).

    I use Data::Dumper to print a complex data structure because it comes in core but there are other libraries for that, like Data::Dump, Data::Printer, and more.