Russ' Bash Stuff

cd Alias Enhancement

This modification to the cd command adds automatic directory intelligence:

cd() {
    builtin cd "$@" || return

    # 1. Display info file in Magenta
    if [[ -f ".dirinfo" ]]; then 
        echo -e "\e[35m$(cat .dirinfo)\e[0m"
    fi

    # 2. Execute file ONLY if owned by you and executable
    # BEWARE: Potential security issue if enabled!
    # if [[ -x ".direxecute" ]]; then
    #     if [[ "$(stat -c "%u" .direxecute)" -eq "$UID" ]]; then
    #         ./.direxecute
    #     else
    #         echo -e "\e[31mSecurity: Skipping .direxecute (Wrong Owner)\e[0m"
    #     fi
    # fi
}
    

li - The Directory Archeologist

A robust directory lister built to handle the unique challenges of WSL and large-scale file management. Features include 3-second safety timeouts, sequence grouping, and space-safe filename parsing.

The Master Perl Source

#!/usr/bin/perl
# fileinfo=The Directory Archeologist - Master Version (v2026.03.02)
use strict;
use warnings;
use File::Basename;
use Getopt::Long;

# --- 1. SETUP & FLAGS ---
my $show_all    = 0;
my $help        = 0;
my $group_mode  = 0;
my $script_name = basename($0);

GetOptions(
    'all|a'   => \$show_all,
    'help|h'  => \$help,
    'group|g' => \$group_mode
) or exit 1;

if ($help) {
    print_help();
    exit;
}

my $term_width = `tput cols` || 80;
my @args = @ARGV;
my $target_dir = ".";
my @ls_flags = qw/-lh --color=always --group-directories-first/;

if (@args == 0) {
    push @args, ".";
} elsif (@args == 1 && -d $args[0]) {
    $target_dir = $args[0];
    $target_dir =~ s|/$||; 
} else {
    push @ls_flags, "-d";
}

# --- 2. EXECUTE LS ---
my $cmd_str = "ls " . join(' ', @ls_flags, ($show_all ? "-a" : ()), map { "\"$_\"" } @args) . " 2>&1";
open(my $ls_fh, "-|", $cmd_str) or die "Could not run ls: $!";
my @ls_lines = <$ls_fh>;
close($ls_fh);

# --- 3. PASS 1: PARSING ---
my %groups; 
my @order;

foreach my $line (@ls_lines) {
    chomp $line;
    next if $line =~ /^total/ || $line =~ /:$/;
    
    if ($line =~ /ls: cannot access '(.+)': No such file or directory/) {
        my $bad_file = $1;
        push @order, "ERR_$bad_file";
        $groups{"ERR_$bad_file"} = { 
            files => [{ name => $bad_file, clean => $bad_file, is_err => 1, bytes => 0, colored => $bad_file }],
            month => "---", day => "--", year => "----"
        };
        next;
    }

    if ($line =~ /^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/) {
        my @m = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
        
        my $colored_name = $m[8];
        my $clean_name = $colored_name; 
        $clean_name =~ s/\x1b\[[0-9;]*m//g;
        $clean_name =~ s/\s+->\s+.*$//;
        $colored_name =~ s/\s+->\s+.*$//;

        my $actual_name = $clean_name;
        $actual_name =~ s/^'//; $actual_name =~ s/'$//;
        next if $actual_name eq '.' || $actual_name eq '..';
        
        my $key = "SINGLE_$actual_name"; 
        my $num = undef;
        if ($group_mode && $actual_name =~ /^(.*?)(\d+)(\D*)$/) {
            $key = "GRP_$1_LEN" . length($2) . "_$3";
            $num = $2;
        }

        if (!$groups{$key}) {
            push @order, $key;
            $groups{$key} = { files => [], month => $m[5], day => $m[6], year => $m[7] };
        }
        
        my $bytes = parse_to_bytes($m[4]);
        push @{$groups{$key}->{files}}, { 
            name => $actual_name, num => $num, line => $line, bytes => $bytes, 
            clean => $clean_name, colored => $colored_name
        };
    }
}

# --- 4. PASS 2: GROUPING ---
my @print_list;
my $max_name_len = 0;
my $max_size_len = 0;

foreach my $key (@order) {
    my $g = $groups{$key};
    my @sorted = sort { ($a->{num}||0) <=> ($b->{num}||0) } @{$g->{files}};
    
    my @sub_ranges;
    my $range_idx = 0;

    if (defined $sorted[0]->{num} && @sorted > 1) {
        for (my $i = 1; $i <= $#sorted; $i++) {
            if ($sorted[$i]->{num} != $sorted[$i-1]->{num} + 1) {
                push @sub_ranges, [ @sorted[$range_idx .. $i-1] ];
                $range_idx = $i;
            }
        }
        push @sub_ranges, [ @sorted[$range_idx .. $#sorted] ];
    } else {
        foreach my $f (@sorted) {
            push @sub_ranges, [$f];
        }
    }

    foreach my $range (@sub_ranges) {
        my @files = @{$range};
        my $total_bytes = 0;
        $total_bytes += $_->{bytes} for @files;
        
        my $s_str = format_bytes($total_bytes);
        my $display_name = (@files > 1) 
            ? "$files[0]->{name} - $files[-1]->{name} (" . scalar(@files) . " files)" 
            : $files[0]->{clean};

        $max_name_len = length($display_name) if length($display_name) > $max_name_len;
        $max_size_len = length($s_str) if length($s_str) > $max_size_len;

        my $orig_name = $files[0]->{name};
        my $full_path = ($target_dir ne "." && ! -e $orig_name && ! -l $orig_name) 
            ? "$target_dir/$orig_name" 
            : $orig_name;
        
        my $priority = (-d $full_path) ? 0 : 1;

        push @print_list, { 
            meta     => [$s_str, $g->{month}, $g->{day}, $g->{year}],
            display  => $display_name,
            is_group => (@files > 1),
            colored  => $files[0]->{colored},
            path     => $orig_name,
            full_path => $full_path,
            is_err   => $files[0]->{is_err} || 0,
            priority => $priority
        };
    }
}

@print_list = sort { 
    $a->{priority} <=> $b->{priority} || 
    lc($a->{display}) cmp lc($b->{display}) 
} @print_list;

# --- 5. PASS 3: PRINT ---
$max_name_len += 2;
my $name_limit = int($term_width * 0.40);
$max_name_len = $name_limit if $max_name_len > $name_limit;

foreach my $p (@print_list) {
    my $full_path = $p->{full_path};
    my ($desc, $desc_color) = ("", "\e[90m");
    my $size_display = $p->{meta}->[0];
    my $colored_name = $p->{is_group} ? "\e[1;33m$p->{display}\e[0m" : $p->{colored};
    
    if ($p->{is_err}) {
        $desc = "!! FILE MISSING OR INACCESSIBLE";
        $desc_color = "\e[1;31m";
        $colored_name = "\e[1;31m$p->{display}\e[0m";
    } elsif (-l $full_path) {
        my $target = readlink($full_path);
        my $exists = -e $full_path;
        my $note = "";
        if (-f "$full_path/.dirinfo") {
            if (open my $nfh, '<', "$full_path/.dirinfo") { 
                $note = <$nfh>; chomp $note; close $nfh; 
            }
        }
        my $note_part = $note ? " [ $note ]" : "";
        if (! $exists) {
            $desc = "!! BROKEN symlink to $target$note_part";
            $desc_color = "\e[1;31m";
            $colored_name = "\e[1;31m$p->{display}\e[0m";
        } else {
            $desc = "symlink to $target$note_part";
            $desc_color = "\e[36m";
        }
    } elsif (-d $full_path) {
        $desc = get_dir_stats($full_path);
        $desc_color = "\e[35m";
    } else {
        $desc = get_file_desc($full_path);
    }
    
    my $meta = sprintf("%${max_size_len}s  %s %2s %5s", $size_display, $p->{meta}->[1], $p->{meta}->[2], $p->{meta}->[3]);
    $desc =~ s/^\#\s*//;
    
    my $prefix_width = length($meta) + $max_name_len + 3;
    my $max_desc_width = $term_width - $prefix_width - 1;
    if (length($desc) > $max_desc_width) {
        $desc = substr($desc, 0, $max_desc_width - 3) . "...";
    }

    my $pad_len = $max_name_len - length($p->{display});
    $pad_len = 1 if $pad_len < 1;
    my $padding = " " x $pad_len;

    printf "%s  %s%s %s%s\e[0m\n", $meta, $colored_name, $padding, $desc_color, $desc;
}

# --- SUBROUTINES ---
sub parse_to_bytes {
    my $val_str = shift || "0";
    my ($val, $unit) = ($val_str =~ /^([\d\.]+)([KMG]?)$/);
    my $b = $val || 0;
    if ($unit) {
        if ($unit eq 'K') { $b *= 1024; }
        elsif ($unit eq 'M') { $b *= 1024 * 1024; }
        elsif ($unit eq 'G') { $b *= 1024 * 1024 * 1024; }
    }
    return $b;
}

sub format_bytes {
    my $b = shift;
    return "0" if $b == 0;
    if ($b < 1024) { return $b; }
    if ($b < 1024 * 1024) { return sprintf("%.1fK", $b/1024); }
    if ($b < 1024 * 1024 * 1024) { return sprintf("%.1fM", $b/(1024*1024)); }
    return sprintf("%.1fG", $b/(1024*1024*1024));
}

sub get_dir_stats {
    my $dir = shift;
    my $cache = "$dir/.li_cache";
    my $mtime = (stat($dir))[9] || 0;
    
    if (-f $cache) {
        if (open my $fh, '<', $cache) {
            my $line = <$fh>;
            close $fh;
            if ($line && $line =~ /^$mtime (.*)/) { return $1; }
        }
    }
    
    my $note = "";
    if (-f "$dir/.dirinfo") {
        if (open my $nfh, '<', "$dir/.dirinfo") { 
            $note = <$nfh>; chomp $note; close $nfh; 
        }
    }

    if (-l $dir) {
        return $note ? "[ $note ]" : ""; 
    }

    my $res;
    eval {
        local $SIG{ALRM} = sub { die "timeout\n" };
        alarm 3;
        
        my $visible_files = `find "$dir" -maxdepth 1 -type f ! -name ".*" 2>/dev/null | wc -l` - 0;
        my $hidden_all = `find "$dir" -maxdepth 1 -type f -name ".*" 2>/dev/null | wc -l` - 0;
        
        foreach my $metafile (qw/.fileinfo .dirinfo .li_cache/) { 
            $hidden_all-- if -f "$dir/$metafile"; 
        }
        
        my $sub_directories = `find "$dir" -maxdepth 1 -type d 2>/dev/null | wc -l` - 1;
        my $total_size = `du -sh "$dir" 2>/dev/null | cut -f1`; 
        chomp $total_size;
        
        my $note_part = $note ? " - $note" : "";
        my $h_str = ($hidden_all > 0) ? " ($hidden_all hidden)" : "";
        
        my $f_str = ($visible_files > 0) 
            ? $visible_files . " file" . ($visible_files == 1 ? "" : "s") . $h_str 
            : ($hidden_all > 0 ? "$hidden_all hidden files" : "");
            
        my $s_str = ($sub_directories > 0) 
            ? ($f_str ne "" ? " & " : "") . $sub_directories . " sub" . ($sub_directories == 1 ? "" : "s") 
            : "";
        
        if ($f_str eq "" && $s_str eq "") {
            $res = "[ empty dir$note_part ]";
        } else {
            $res = "[ $f_str$s_str in $total_size$note_part ]";
        }
        
        alarm 0;
    };

    if ($@) {
        my $note_part = $note ? " - $note" : "";
        $res = ($@ eq "timeout\n") ? "[ SCAN TIMEOUT$note_part ]" : "[ SCAN ERROR$note_part ]";
    }

    if (-w $dir && $res !~ /TIMEOUT|ERROR/) { 
        if (open my $cfh, '>', $cache) { 
            print $cfh "$mtime $res"; close $cfh; 
        }
    }
    return $res;
}

sub get_file_desc {
    my $file_path = shift;
    my $file_name = basename($file_path);
    my $parent_dir = dirname($file_path);
    
    if (-T $file_path && open(my $fh, '<', $file_path)) {
        while (<$fh>) { 
            if (/fileinfo=(.*)/) {
                my $found = $1;
                close $fh;
                return "# $found";
            }
            last if $. > 10; 
        }
        close $fh;
    }
    
    my $dot_fileinfo = "$parent_dir/.fileinfo";
    if (-f $dot_fileinfo) {
        if (open my $mfh, '<', $dot_fileinfo) {
            while (<$mfh>) {
                next if /^\s*#/ || ! /=/;
                my ($pattern, $description) = split('=', $_, 2);
                chomp $description;
                $pattern =~ s/\*/.*/g; 
                if ($file_name =~ /^$pattern$/) {
                    close $mfh;
                    return "# $description";
                }
            }
            close $mfh;
        }
    }
    
    my $file_type = `file -b "$file_path" 2>/dev/null`; 
    chomp $file_type;
    return $file_type || "unknown file type";
}

sub print_help {
    print <<EOF;
Usage: $script_name [options] [path]
Options:
  -a, --all    Show hidden files
  -g, --group  Consolidate numbered sequences
  -h, --help   Show this help message
EOF
}