Yes, you can't do it with sort alone unless you pre- and post- process the list in a decorate-sort-undecorate fashion.
Here you can use zsh whose globs you can sort using arbitrary transformations.
$ print -rl -- *(oe['REPLY=${(M)REPLY%-*-*-*}-$REPLY'])
file1-2025-09-30.tgz
file2-2025-09-30.tgz
file3-2025-09-30.tgz
special-file-2025-09-30.tgz
yet-another-file-2025-09-30.tgz
file1-2025-10-01.tgz
file2-2025-10-01.tgz
file3-2025-10-01.tgz
special-file-2025-10-01.tgz
yet-another-file-2025-10-01.tgz
file1-2025-10-15.tgz
file2-2025-10-15.tgz
file3-2025-10-15.tgz
special-file-2025-10-15.tgz
yet-another-file-2025-10-15.tgz
For example, where we use oe[code] to define the order based on the evaluation of the code (with $REPLY the name of the file to work on).
${(M)REPLY%-*-*-*} expands to the shortest part of $REPLY at the end that Matches -*-*-* so on special-file-2025-10-15.tgz for instance, expands to -2025-10-15.tgz to which we append $REPLY so we end up sorting based on -2025-10-15.tgz-special-file-2025-10-15.tgz instead of special-file-2025-10-15.tgz
The list is passed to print -rl instead of ls -d as ls by default does its own sorting. Though if your ls is GNU ls you can add the -U option to disable that sorting.
Another approach:
print -rl -- *(oe['REPLY[1,0]=$REPLY[(ws[-])-3,-1]-'])
Where we get the third-last (-3) to last (-1) words of $REPLY (those words being --separated) and prepend to $REPLY by assigning to $REPLY[1,0].
If you don't have zsh, you could use perl:
perl -le '
print $_->[0] for
sort {$a->[1] cmp $b->[1]}
map {[$_, s/(.*)((-.*){3})/$2-$1/sr]} @ARGV' -- *
Where you'll recognise a typical Schwartzian transform, which is essentially what zsh does with its oe glob qualifier.
Here the list of (non-hidden) files is passed to perl via the expansion of the shell glob *, but you could also use perl's own glob:
perl -le '
print $_->[0] for
sort {$a->[1] cmp $b->[1]}
map {[$_, s/(.*)((-.*){3})/$2-$1/sr]} <*>'
Which would also have the advantage of avoiding:
- the limit on the size of arguments passed to a command.
- issues when there's no non-hidden file in the current directory.
To pass those to a command such as GNU ls -ogdU --, change it to:
perl -le '
exec qw(ls -ogdU --), map {$_->[0]}
sort {$a->[1] cmp $b->[1]}
map {[$_, s/(.*)((-.*){3})/$2-$1/sr]} <*>'
Though that reintroduces the problem with the limit on size of arguments which you could work around with:
perl -l0e '
print $_->[0] for
sort {$a->[1] cmp $b->[1]}
map {[$_, s/(.*)((-.*){3})/$2-$1/sr]} <*>' |
xargs -r0 ls -ogdU --
printing the list 0-byte-delimited (0 being the only byte value that can't be found in a file path) and using xargs to split that list into as many invocations of ls as necessary to avoid that limit; zsh has its own zargs for that.
Note that zsh globs and sort sort files based on the locale's collation¹ while perl's cmp compares byte-wise à la memcmp().
¹ though see the n qualifier in zsh or the -V/--sort=version of GNU sort to take into account the numeric value of sequences of digits within the strings; for a YYYY-MM-DD date, they should all be equivalent.
sortimplementations don't have "--key" and "--field-separator" options. Those are non-standard alternative names by the GNU implementation ofsortfor the standard-kand-toptions.