Diskusage

CGM : It's an invariable rule that however many megabytes/gigabytes/terabytes of disk space you have, eventually they will fill up. When that happens the next step is usually a frantic rush to see what old data can be deleted or compressed to make space for new stuff. This script is designed to help with that process.

It basically just runs the Unix command du ("Disk Usage") and presents the output as a graphical tree, sorted so that the biggest directories/files are at the top. Once you have identified the files you want to delete/compress/etc., you can select them in the tree and copy/paste the pathnames to a shell.

Diskusage image2

Notes:

  • The script needs the treectrl and Tklib tooltip packages.
  • It needs the Gnu version of du since that can also return file/directory modification times.
  • I experimented with making the script self-contained by not calling du but having the script read the file sizes itself as is done in the du script on this wiki. However this turned out to be significantly slower, which matters when you are trying to scan a big disk in a hurry. Also the separate du program accounts correctly for odd cases like files with multiple hard links, or sparse files where not all the data blocks actually exist on disk.

#!/usr/local/bin/tcl

# diskusage - display disk space used in tree gui
# C. Macleod 24/12/10 - 6/9/20

if {[catch {package require Tk}]} {
    puts stderr "This program needs an X-window display."
    exit
}

########################### Set up screen ############################

package require treectrl
package require tooltip

treectrl .tc -yscrollcommand {.ys set} -xscrollcommand {.xs set} -width 600 -height 400
.tc column create -text {Size kB}
.tc column create -text {Mod Date}
.tc column create -text {File Name}
.tc element create el1 text
.tc style create s1
.tc style elements s1 el1
.tc style layout s1 el1 -ipadx 2
.tc element configure el1 -fill {red active green selected}
.tc configure -treecolumn 0 -selectmode extended
tooltip::tooltip .tc {Select files/directories with keyboard or mouse.
Control-C copies selected pathname(s).
Right-click to copy Item/Pathnames/Details.}

scrollbar .ys -command {.tc yview} -orient vertical
scrollbar .xs -command {.tc xview} -orient horizontal
grid .tc .ys
grid .tc -sticky nsew
grid .ys -sticky ns
grid .xs -sticky ew

grid columnconfigure . 0 -weight 1
grid columnconfigure . 1 -weight 0
grid rowconfigure . 0 -weight 1
grid rowconfigure . 1 -weight 0

##################### Get and display sizes under specified path ######################

proc read_sizes ppath {

    set ::parent root
    set ::old_depth [llength [file split $ppath]]

    set cmd "| du -a -k --time --threshold=$::min_size"
    lappend cmd $ppath
    set du [open $cmd]
    fileevent $du readable [list read_item $du]
}

proc read_item du {

    global parent old_depth

    if {[gets $du line] == -1} {
        catch {close $du}
        incr ::done
        return
    }

    if {! [regexp -- {(\d+)\s+([\d\-]+\s[\d:]+)\s+(.*)} $line - size mtime path]} continue

    set depth [llength [file split $path]]

    for {set x $old_depth} {$x <= $depth} {incr x} {

        set isdir [expr {$x < $depth}]
        set isopen [expr {[.tc depth $parent] < 1}]

        set item [.tc item create -parent $parent -button $isdir -open $isopen]
        .tc item style set $item 0 s1 1 s1 2 s1
        if {$isdir} {
            set parent $item
            .tc item text $item 0 0
            set dpath $path
            for {set y $x} {$y < $depth} {incr y} {
                set dpath [file dir $dpath]
            }
            .tc item text $item 2 $dpath
        }
    }
    if {$depth < $old_depth} {
        set item $parent
        set parent [.tc item parent $item]
    }

    .tc item text $item 1 $mtime
    .tc item text $item 2 $path
    .tc item text $item 0 $size

    set old_depth $depth

    if {[.tc item isopen $parent]} {
        .tc item sort $parent -integer -decreasing
    }

    maybe_update
}

#################### Expand directory #####################
.tc notify bind .tc <Expand-after> { expand %I }

proc expand item {
    .tc item sort $item -integer -decreasing
}

############# Update display but not more than once per 5 sec ##############

set next_update_time [clock seconds]

proc maybe_update {} {
    set now [clock seconds]
    if {$now >= $::next_update_time} {
        set ::next_update_time [expr {$now + 2}]
        update
    }
}

#################### Copy on right click or ctrl-C #####################

set my_selection {}

proc my_copy text {
    set ::my_selection $text
    selection own .
}

selection handle . onSelect

proc onSelect {offset maxChars} {
    return [string range $::my_selection $offset [expr {$offset+$maxChars}]]
}

menu .rmenu -tearoff 0

bind .tc <3> "right_click %x %y %X %Y"
bind .tc <Control-a> {.tc selection add first last}
bind .tc <Control-c> copy_pathnames

proc right_click {x y X Y} {
    .tc identify -array ident $x $y
    if {$ident(where) eq "item"} {
        set text [.tc item text $ident(item) $ident(column)]
    } else {
        set text {}
    }
    .rmenu delete 0 end
    .rmenu add command -label "Copy item '$text'" -command [list my_copy $text]
    .rmenu add command -label "Copy selected pathnames" -accelerator ^C -command copy_pathnames
    .rmenu add command -label "Copy selected size/date/pathnames" -command copy_details
    tk_popup .rmenu $X $Y
}

proc selected_pathnames {} {
    set text {}
    foreach item [.tc selection get 0 end] {
        lappend text [.tc item text $item 2]
    }
    return $text
}

proc copy_pathnames {} {my_copy [selected_pathnames]}

proc copy_details {} {
    set text {}
    foreach item [.tc selection get 0 end] {
        append text "[join [.tc item text $item] \t]\n"
    }
    my_copy $text
}

########################### Start things up ############################

set min_size 100K

if {[llength $argv]} {
    set filelist $argv
} else {
    set filelist .
}
wm title . "Disk Usage: [pwd] $filelist"
. configure -cursor watch
focus .tc
update

foreach f $filelist {
    read_sizes $f
    vwait done
}

. config -cursor {}

See also : Tktreemap