Friday, September 4, 2009

How to sort human readable du output

I originally ripped my movies to my hard drive in divx/avi, but I have redone them in h264/mp4 and I now wish to compare the spaces they take. Easy enough -- du -ch *.mp4 and du -ch *.avi. But I found this to be a bit unsatisfactory. I then wanted to sort the output of all the files. No problem again -- du | sort -rn. But what if I want it to be human-readable? Ah, there's the problem -- 741M is sorted as bigger than 1.4G because sort doesn't know the difference. So I thought to myself, "What if I could take the output as blocks, do the sort, and then transform the blocks into human readable format?" Voila! The following statement was born:


du -cm *.mp4 | sort -n | awk '{ printf "%s GB -",$1/1000; for (i=2;i<=NF+1;i++) printf "%s%s",i==NF+1 ? ORS : FS,$i }'


Note the use of the -m flag on du to convert it to megabytes first -- this means I only have to divide by 1000 in the awk script. Despite the obvious advantage of being able to sort by human-readable format, it also has the advantage of keeping it more accurate -- a "du -h" would display 1.4G where my method displays 1.359. Essentially I take the output in megabytes, do an ascending sort, and then use awk to manipulate the numeric field and display the rest of the fields as is. Also, I originally used 'print' for the first 'printf' but this automatically appended a '\n' so I switched it to a printf without a '\n'. When $NF+1 is evaluated in the for loop, ORS (output record separator, '\n' by default) is output, but when $2-$NF are evaluated, FS (field separator, [space] by default) is output.


UPDATE:

As a recursive variant, this appears to work well:


du -cam . | grep .mp4$ | sort -n | awk '{ printf "%s GB -",$1/1000; for (i=2;i<=NF+1;i++) printf "%s%s",i==NF+1 ? ORS : FS,$i }'