Skip to content
Advertisement

Possible to combine glob sort w/ alternation in zsh?

Looking for a shell-only[1] way to take this list of dirs:

foo1.1
foo1.10
foo1.10/bar
foo1.2
foo1.3
foo1.3/bar
foo1.4
foo1.5
foo1.5/bar
foo1.6
foo1.7
foo1.8
foo1.9
foo2.1

And return it sorted numerically, with the subdirs showing up right after their parent:

foo1.1
foo1.2
foo1.3
foo1.3/bar
foo1.4
foo1.5
foo1.5/bar
foo1.6
foo1.7
foo1.8
foo1.9
foo1.10
foo1.10/bar
foo2.1

(*|*/bar)(n) is rejected as a bad pattern, while */{,bar}(n) expands to */(n) ~/bar(n) so the subdirs show up at the end.

[1] I need this to be able to work on a wide variety of systems, so using GNU sort’s -V or GNU ls’s -v or the like won’t work.

Advertisement

Answer

You can easily do it in two steps: list, then sort.

dirs=(* */bar)
dirs=(${(on)dirs})

This relies on / being sorted before any suffix of an existing directory, so it won’t work if you have directories with names like foo1.3-qux (it would be sorted between foo1.3 and foo1.3/bar. Since all the files are directories, you could work around that with a temporary / suffix, if it’s ok to have foo1.3-qux sorted before foo1.3:

dirs=(*/ */bar)
dirs=(${${(on)dirs}%/})

For more robustness you can temporarily replace the / characters by a null byte, which is both guaranteed to be sorted before any other (at least in the C locale) and not to appear in a file name.

dirs=(* */bar)
dirs=(${${(on)${dirs////$''}}//$''//})
Advertisement