9

I'm looking to list the entire contents of a directory, including the contents of subfolders but sorted by filesize. Thus far I've managed to get as far as listing and sorting whilst still being recursive with ls -lhSR(the h is nice to have but definitely not essential for me, as long as I can get file sizes). I am likely overlooking something obvious, or asking the impossible, but any advice here would be greatly appreciated.

5 Answers 5

15

You can use find:

find . -type f -printf "%s %P\n" | sort -n

Optional: To convert byte values to human-readable format, add this:

| numfmt --to=iec-i --field=1

Explanation:

 find in current directory (.) all files (-type f) 

 -printf: suppress normal output and print the following:
     %s - size in bytes
     %P - path to file
     \n - new line

 | sort -n: sort the result (-n = numeric)
8
  • Oh perfect! Thank you so much, this did exactly what I needed!
    – user806346
    Jun 14, 2018 at 14:49
  • 3
    I'm glad my answer helped you. Please consider clicking the checkmark on the left side to mark the answer accepted. Thanks.
    – pLumo
    Jun 14, 2018 at 14:58
  • 1
    @RoVo it would also be nice to upvote the question since, given that you've answered it, you probably found it interesting and useful.
    – terdon
    Jun 14, 2018 at 15:12
  • This is indeed true and I did this now ;-)
    – pLumo
    Jun 14, 2018 at 15:14
  • @toms It's OK to wait a while (maybe a day or so) to accept the answer, even when it's as good as this one. Once the answer is accepted, there's no way for another answer to turn out to be even better. And because of that, a lot of people won't bother to submit any other answers, so we don't get a chance to see them to find out if one's better. Jun 14, 2018 at 16:51
7

Since you didn't specify a particular shell, here's an alternative using zsh's glob qualifiers with

setopt extendedglob

for the recursion. Then for example:

  1. recursively list plain files:

    printf '%s\n' **/*(.)
    
  2. recursively list plain files, ordered by increasing Length (i.e. size):

    printf '%s\n' **/*(.oL)
    
  3. recursively list plain files, Ordered by decreasing size:

    printf '%s\n' **/*(.OL)
    
  4. recursively list plain files, ordered by decreasing size, and select the top 3 results:

    printf '%s\n' **/*(.OL[1,3])
    

If you want the file sizes as well, then you could use

du -hb **/*(.OL[1,3])
4

With the globstar shell option set you can use shell globbing:

shopt -s globstar         # don’t match hidden files
shopt -s globstar dotglob # match hidden files
stat -c"%s %n" **/* | sort -n

If you try that with too many files, you‘ll get an “Argument list too long” error. To work around that, you can use printf and xargs:

printf "%s\0" **/* | xargs -0 stat -c"%s %n" | sort -n

I just realized this prints the directories (with a size of 4096 bytes) as well – if you don’t want that, use this instead:

stat -c"%A %s %n" **/* | sed '/^d/d;s/\S* //' | sort -n
printf "%s\0" **/* | xargs -0 stat -c"%A %s %n" | sed '/^d/d;s/\S* //' | sort -n

Example run

$ tree
.
├── edits.png
├── makescript
├── new
│   └── edits.png
└── test
    └── 1.png

2 directories, 4 files
$ stat -c"%s %n" **/* | sort -n
0 test/1.png
43 makescript
2160 edits.png
2160 new/edits.png
4096 new
4096 test
$ stat -c"%A %s %n" **/* | sed '/^d/d;s/\S* //' | sort -n
0 test/1.png
43 makescript
2160 edits.png
2160 new/edits.png
6
  • Nice solution. Compared to find, it doesn't include hidden files, how to achieve that?
    – pLumo
    Jun 15, 2018 at 8:47
  • @RoVo Always forget about these – you just need to set the dotglob shell option, see my updated answer.
    – dessert
    Jun 15, 2018 at 9:41
  • Instead of stripping directories after the fact with sed, you could consider something like printf "%s\0" **/* | xargs -0 sh -c 'for f; do [ -d "$f" ] || stat -c "%s %n" "$f"; done' sh | sort -n Jun 15, 2018 at 12:43
  • You can use ls -lhSd **/* if you don't mind having the directories as part of the list. Or if none of your directory names have . in them, and all the files you want do, you can ll -hS **/*.*, or similar. Jun 15, 2018 at 12:57
  • turned that into an answer Jun 15, 2018 at 13:20
3

If you don't have zsh, you can still use du + sort:

  1. Human-readable sizes, including cumulative sizes of directories:

    du --apparent-size -ah0 . | sort -zh | xargs -0L1
    
  2. Only files (using find):

    find . -type f -print0 |
      du --files0-from=- --apparent-size -ah0 |
      sort -zh |
      xargs -0L1
    

In both cases, I have opted to use null-terminated lines (-0, -z, -print0 options), to be safe against all valid filenames.

0
0

For quick interactive use on directory trees that aren't too huge, shopt -s globstar is really nice. A glob can't filter out directories based on type, but if you use it with ls -d then ls will just print the directory name, instead of the contents.

Assuming your ll alias includes -lh:

  # with  shopt -s globstar   in your .bashrc
ll -rSd **/*

will give you output like this (from my code-golf directory), but with colour highlighting (so it's easier to see the directories). Note that sorting by filesize happened across subdirectories.

drwxr-xr-x 1 peter peter   70 Jun  8 07:56 casexchg
...
drwxr-xr-x 1 peter peter  342 Mar 13 18:47 parity-party
-rw-r--r-- 1 peter peter  387 Jul 29  2017 likely.cpp
-rw-r--r-- 1 peter peter  416 Aug 31  2017 true-binary.asm~
-rw-r--r-- 1 peter peter  447 Feb 23 20:14 weight-of-zero.asm
...
-rw-r--r-- 1 peter peter 6.4K Jun  1  2017 string-exponential.asm
-rwxr-xr-x 1 peter peter 6.7K Aug 31  2017 true-binary
-rwxr-xr-x 1 peter peter 6.8K Sep 17  2017 dizzy-integer
-rw-r--r-- 1 peter peter 7.5K Jul 24  2017 fibonacci/fibonacci-1G.v3-working-32b-stack-except-output.asm
-rw-r--r-- 1 peter peter 8.4K Jul 25  2017 fibonacci/perf.32bit-pop-114limb.sub-cmc.1G~
-rw-r--r-- 1 peter peter 8.4K Jul 25  2017 fibonacci/perf.32bit-pop-114limb.sub-cmc.1G
-rwxr-xr-x 1 peter peter 8.4K May 19 04:29 a.out
-rw-r--r-- 1 peter peter 8.9K Jul 25  2017 fibonacci/perf.python-xnor-2n
-rw-r--r-- 1 peter peter 9.5K Jul 26  2017 fibonacci/fibonacci-1G-performance.asm
-rwxr-xr-x 1 peter peter 9.6K Apr 12 23:25 empty-args
-rw-r--r-- 1 peter peter 9.7K Dec 18 17:00 bubblesort.asm
-rwxr-xr-x 1 peter peter 9.9K Feb  6 23:34 parity-party/a.out
-rw-r--r-- 1 peter peter 9.9K Jul 25  2017 fibonacci/fibonacci-1G-performance.asm~
...

You could filter out the directories by piping through grep -v '^d'

You can sometimes use a glob that matches only files and not directories, if your filenames have a pattern. e.g. ll -rSd **/*.jpg, or even **/*.* works if none of your directory names have . in them, and all the files you want do.

(For people with a DOS background: there's nothing magical about *.* on Unix. It just matches any directory entry that contains a literal dot. But other than executables and sometimes text files, it's common to give extensions to filenames.)

@dessert points out you would need shopt -s dotglob for it to match all files.


With GNU find

If there aren't too many files to fit on one ls command line, find -exec ls {} + will put them all on on command line where ls can sort them.

find -not -type d -exec ls --color -lrSh {} +

Using -not -type d instead of -type f avoids ignoring symlinks, named pipes, sockets, device files, and whatever else you have kicking around in your directories.


With du:

du -ach | sort -h
....
4.0K    x86-modedetect-polyglot.o
8.0K    ascii-compress-base.asm
8.0K    dizzy-integer
8.0K    stopwatch-rdtsc.asm
8.0K    string-exponential.asm
8.0K    true-binary
12K     a.out
12K     bubblesort.asm
12K     casexchg
12K     empty-args
100K    parity-party
220K    fibonacci
628K    total

Now directory names are sorted into the list with to sum total of all their contents, but individual files are still included.

sort -h, aka --human-numeric-sort, sorts numbers with size suffixes like du -h prints. It's perfect for use with du.

I often use du -sch * | sort -h, or */ to get only directories.

du -sch **/* | sort -h would give you the above output, if you forget that du has a -a option.

(I only took the time to look it up because I'm posting an answer. For interactive use, I probably would have just used du -sch **/*.

You must log in to answer this question.