Skip to content

Instantly share code, notes, and snippets.

@dhilst
Last active December 7, 2025 18:02
Show Gist options
  • Select an option

  • Save dhilst/27f524641e0af5cc3ca2327dced0da85 to your computer and use it in GitHub Desktop.

Select an option

Save dhilst/27f524641e0af5cc3ca2327dced0da85 to your computer and use it in GitHub Desktop.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use File::Find;
use Getopt::Long qw(GetOptions :config no_bundling);
use Data::Dumper qw(Dumper);
my %files_sizes;
my %child_sizes;
my %opts = (
max => 2,
K => 0,
xdev => 0,
);
sub usage {
say STDERR "Usage: $0 <PATH> [-maxdepth=3] [-K] [-xdev]";
say STDERR " -K ........ print in K bytes instead of human readable size";
say STDERR " -xdev ...... do not cross filesystems";
exit -1;
}
usage() unless @ARGV >= 1;
my $path = shift @ARGV;
my $pdepth = $path =~ tr{/}{};
my $root_dev = devnum($path);
GetOptions(
"maxdepth=i" => \$opts{max},
"K" => \$opts{K},
"xdev" => \$opts{xdev},
) or usage();
sub devnum {
my $arg = shift // $_;
my @stats = stat($arg)
or die "stat $arg: $!";
$stats[0];
}
sub to_human_size {
my ($size) = @_;
my @suffixes = ("K", "M", "G", "T");
my $idx = 0;
while ($size > 1024 and ++$idx < @suffixes) {
$size /= 1024;
}
$idx-- unless $idx < @suffixes;
sprintf "%d%s", $size, $suffixes[$idx];
}
sub depth {
($_[0] =~ tr {/}{}) - $pdepth;
}
sub du_siz {
(split /\s+/, `du -sb "$_[0]" 2> /dev/null`, 2)[0];
}
sub fill_file_sizes {
find({
wanted => sub {
my $dir = $File::Find::dir;
my $name = $File::Find::name;
return unless -d || -f;
if ($opts{xdev} && devnum($name) != $root_dev) {
$File::Find::prune = 1;
return;
}
if (/^\/(proc|sys|dev)/) {
$File::Find::prune = 1;
return;
}
if (-d && depth($name) >= $opts{max}) {
$files_sizes{$name} = du_siz $name;
$File::Find::prune = 1;
return;
}
$files_sizes{$dir} += -s if -f;
$files_sizes{$name} += -s if -d;
},
no_chdir => 1,
follow => 0,
}, $path);
}
sub fill_child_sizes {
for my $dir (keys %files_sizes) {
next if depth($dir) >= $opts{max};
my $qdir = quotemeta $dir;
my @children = grep { /^$qdir\// } keys %files_sizes;
my $siz = $files_sizes{$dir};
$siz += $files_sizes{$_} for @children;
$child_sizes{$dir} = $siz / 1024;
}
}
sub print_child_sizes {
for my $dir (sort {
$child_sizes{$b} <=> $child_sizes{$a}
} keys %child_sizes) {
my $siz = sprintf "%d", $child_sizes{$dir};
$siz = to_human_size($siz) unless $opts{K};
say sprintf "%s %s", $siz, $dir;
}
}
sub main {
fill_file_sizes;
fill_child_sizes;
print_child_sizes;
}
main;
#!/bin/sh
# Or you can simply:
du --one-file-system --max-depth=3 -h $@ | sort -hrk1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment