0

I have a bunch of directories each containing a bunch of files. Normally I can list all of the files with ls */*.* but this always lists them according to alphabetical order on the directories, then alphabetical order within each directory. So it will always output

dir1/file1.foo
dir1/file2.foo
...
dir2/file1.foo
dir2/file2.foo
...
...
dirN/file1.foo
dirN/file2.foo
...

What I'd like it to do instead is output it so that the directories are arranged in some specific order (for example, in reverse), but have all the files within each directory in normal order, like so:

dirN/file1.foo
dirN/file2.foo
...
...
dir1/file1.foo
dir1/file2.foo
...

Using ls -r */*.* doesn't do what I want because it reverses the order within each directory as well as the order of the directories themselves. I have tried using ls `ls -r` which does what I want for the directory names that have no spaces in them, but won't work for the ones that do (which is unfortunately most of them). If I try instead ls "`ls -r`" then it outputs the same thing as ls -r - a list of directory names, but in reverse. How do I get bash to do what I want?

1

Using the sort command:

ls */* | sort -rst '/' -k1,1

with:

  • -t '/' to change the field separator
  • -r to do a reverse sort
  • -s to ensure the sort is stable (in other words, it doesn't change order beyond the specified columns
  • -k1,1 to restrict the sort to the first column

This assumes that the output of ls */* is already sorted (which it should be), otherwise add a plain sort stage in the middle.

0

via zsh script

As with most things in Linux/Unix, there is more then one way of doing things. For better or worse, and for what its worth, this is another way of doing the same thing.

#!/usr/bin/env zsh
Dirs=(*(/D))  # (/D) = /(only directories) D(include hidden or dot)
for D in ${(O)Dirs}  # (O) = reverse sort
do
    L=(${D}/*(ND))  # (ND) = N(in case the glob is empty) D(see above)
    [ ${#L} -eq 0 ] && { print "${D}/" ; continue }  # if empty glob
    print -l ${(o)L}  # (o) = normal sort
done

print zsh builtin -l outputs each element on its own line.

Leave off the D glob qualifier if you don't want to list hidden directories and files.

0

Supplementing @xeniod's answer (started as a comment but got too long): as per the original requirement of sorting directories in reverse, and files in alphanumeric order:

$ ls -d */* | sort -s -t '/' -k1,1rn -k2,2

Or, if files/directories have more than one digit, e.g., instead of just dir1..dir9, you also have dir10, dir11, dir101,... and files file1..file10..file101..., you can sort as "version numbers" using option -V, vs just alpha/numeric sort (only supported on GNU sort):

$ ls -d */* | sort -s -t '/' -k1,1rV -k2,2V

Ideally, though, your directories & files will have some special character prefixing (or delimiting) the fields you want to sort on like a dash ("-"), e.g dir-23/file-1.txt, dir-902/file-203.txt, etc, so that you can sort just on those fields (non-numeric text after the number is ignored):

$ ls -d */* | sort -s -t- -k2,2nr  -k3,3n

Key points:

  • add multiple -k{begin,end} to sort columns between delimiters / separately, e.g., sort first column as alphanumeric, second as reverse-numeric, third as "version numbers", etc
  • I added "-d" to "ls -d */*", so that directory contents aren't listed, which will break the sort if they exist. Alternatively, to only consider files, could use find */ -maxdepth 1 -type f instead of ls.)
  • Note that you really don't even care about "/" in your sort, which is usually fine. Note you can't have more than one -t {delim} option in a single sort anyway (eg -t '-' -k... -t '/'...)

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.