2

Given a tree of files like this:

subdirFoo/2019-06-01-blah.ext
subdirFoo/2019-06-07-blah.ext
subdirFoo/2019-05-02-blah.ext
subdirBar/2019-06-03-blah.ext
subdirBar/2019-05-05-blah.ext
subdirBar/2019-05-13-blah.ext

I want to iterate over these files and pass them as arguments to somecommand, BUT, I want to iterate over them in sorted order by the FILENAMES, not the full paths.

Normally, globs are sorted alphabetically, but I get them in this order:

subdirBar/2019-05-05-blah.ext
subdirBar/2019-05-13-blah.ext
subdirBar/2019-06-03-blah.ext
subdirFoo/2019-05-02-blah.ext
subdirFoo/2019-06-01-blah.ext
subdirFoo/2019-06-07-blah.ext

meaning to say, the subdirectories are considered in the sorting, which is not what I want.

How can I get the glob to be sorted by filename only, but still keep the subdir in the loop so that the command can reference the file correctly? i.e. stripping off the subdir won't work because then the path to the file is incorrect/lost.

i.e. I need them in this order:

subdirFoo/2019-05-02-blah.ext
subdirBar/2019-05-05-blah.ext
subdirBar/2019-05-13-blah.ext
subdirFoo/2019-06-01-blah.ext
subdirBar/2019-06-03-blah.ext
subdirFoo/2019-06-07-blah.ext

Solution in zsh preferred, but I can accept bash or sh.

3 Answers 3

1

You can use sort you will need to set the field delimiter to /, and sort on the 2nd field.

3
  • Right, something like printf '%s\n' fileglob* | sort -k2 -d/ | xargs somecommand, and hope theres no newlines in the fnames. (I assumed -d for delimiter, not at my terminal rn) Nov 16, 2019 at 18:59
  • The sort in gnu coreutils has a -z option. It uses the nul character as the record delimiter. And -0 for xargs. nul is not used in filenames. Nov 16, 2019 at 20:39
  • Perfect: i couldn’t remember if sort would do that or not, so I stayed away from it. Obviously preferable Nov 16, 2019 at 20:52
1

I don't know if it's the right way to do it, but you could stuff a ${VAR##*/} parameter substitution inside a zsh oe glob qualifier:

 % printf '%s\n' subdir*/*(.Noe:'REPLY=${REPLY##*/}':)
subdirFoo/2019-05-02-blah.ext
subdirBar/2019-05-05-blah.ext
subdirBar/2019-05-13-blah.ext
subdirFoo/2019-06-01-blah.ext
subdirBar/2019-06-03-blah.ext
subdirFoo/2019-06-07-blah.ext

This orders the result of the glob by the last path component (the filenames). The :'...': bit in the glob qualifier is executed for each match, and REPLY is set to a value that will be used as the sorting key ($REPLY is the actual match from the start).

See for example the Glob Qualifiers subsection of zsh: 14.7 Filename Expansion

oe and o+ are special cases; they are each followed by shell code, delimited as for the e glob qualifier and the + glob qualifier respectively (see above). The code is executed for each matched file with the parameter REPLY set to the name of the file on entry and globsort appended to zsh_eval_context. The code should modify the parameter REPLY in some fashion. On return, the value of the parameter is used instead of the file name as the string on which to sort. Unlike other sort operators, oe and o+ may be repeated, but note that the maximum number of sort operators of any kind that may appear in any glob expression is 12.

7
  • Yes, this is similar to what was suggested to me for one of my questions, but the intent was a bit different: unix.stackexchange.com/questions/551160/…
    – Kusalananda
    Nov 16, 2019 at 18:42
  • Okay, this does seem to work, but holy guacamole, how is anyone supposed to remember the syntax of such a monstrosity? :) EDIT: Actually, this doesn't work. It still sorts by subdir first before sorting on filename.
    – Pistos
    Nov 16, 2019 at 18:49
  • 1
    @Pistos Like everything, by using it. It's actually quite simple once you get used to it. The (...) modifies the behaviour of the globbing pattern, the . selects only regular files, the N acts as nullglob in bash, and o orders the result according to some criteria. The criteria here is e which means "execute the following code for each match and use whatever is left in REPLY as the sorting key for that match". The two : are delimiters for the code to execute.
    – Kusalananda
    Nov 16, 2019 at 18:52
  • @Pistos steeldriver's command works, and is the correct solution for the zsh shell (it graciously handles any type of filename, which sort does not, unless you use GNU options). If you tested it with ls, know that ls does its own sorting. If you wish to use ls, make sure it doesn't sort: ls -f
    – Kusalananda
    Nov 16, 2019 at 18:56
  • 2
    @Pistos Ah, yes. The brace expansion would expand to two patterns, one after the other. Those two patterns would then be expanded to generate two separate lists, each separately sorted. This is why you don't get the wanted effect. Better then to use (somedir|someotherdir) in place of that brace expansion.
    – Kusalananda
    Nov 16, 2019 at 20:10
-1

I ended up using find instead of globs. Thanks to @ctrl-alt-delor for the hint about sorting by field.

for file in `find {subdirFoo,subdirBar} -maxdepth 1 -name 'prefix*.ext' | sort -t / -k 2`; do echo $file; somecommand $file; done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .