3

I'm almost certain that this is not something that the zsh shell provides a way of doing, but I thought I'd ask anyway just to make sure I'm not missing anything from the manual.

With the zsh shell, I can pick out the two largest visible files from a directory with the pattern

*(.OL[1,2])

If I have a set of directories, and I would want to have the two largest files from each, I believe I would have to loop over the individual directories and then use

$dirpath/*(.OL[1,2])

(where $dirpath is the directory path in the current iteration of the loop).

It would be nice to be able to say

*/*(.OL[1,2])

but that glob qualifier would apply to the list of matching names as a whole, and I would get two matches, not two from each directory.

Question: Would it be possible to limit the "scope" of the qualifier to only affect the most recent path component somehow?

1

Another approach could be something like:

print -rC1 -- *(N-/e['reply=($REPLY/*(N.OL[1,2]))'])

One can set the $reply array in the expression glob qualifier to further return a list of files. So here, we glob the list of dirs or symlinks to dirs and for each, return the 2 largest regular files.

Note that the e qualifier syntax is e:code:, where one can use other characters than : for the delimiter, or pairs like e(code), e[code]. But note that word expansions are performed in the code part, actually anywhere in the glob qualifiers which is good because you can use variables in there like *(L+$min_size), but here means you generally need to put the code inside single quotes to prevent the expansions.

Now, (and it's a trick I learnt relatively recently) if instead of :, you use a character that happens to be wildcard character (which includes ?, * but also the [, ] pair) as the delimiter and leave the delimiters outside the quotes, then you don't need to worry whether that character may appear in the code, because an unquoted * delimiter can only be matched by another unquoted *, and same for [ vs ].

So *(N-/e['reply=($REPLY/*(N.OL[1,2]))']) works even though ] happens to occur within the (quoted) code delimited by unquoted [...]. It wouldn't work with other delimiters like :...: or (...) or {...}.

  • Yes, and the directory is passed in $REPLY into the e expression? Yes, this looks very much like what I was expecting when I set out, although Gilles' "brace expansion"-like solution was also good. I will have to turn it over for a bit to see what feels most natural to use. – Kusalananda Nov 14 '19 at 21:28
  • Yes, this is the more natural fit for the way I'm thinking. – Kusalananda Nov 15 '19 at 8:15
  • @Kusalananda. Personally, I generally use @Gilles's approach. Especially at the prompt, where I'd do a=(*(/)), possibly edit the array with vared a, and then reuse that array possibly several times with trial and error with $^a. Also note that using e['reply(...)'] is hard to type and not much shorter than using the short loop forms for f (*(/)) cmd -- $f/*... (and in the end, that's just another form of loop so not much different). – Stéphane Chazelas Nov 15 '19 at 9:38
  • Well, the syntax in your answer is ever so slightly more verbose, but it it's more in line with my way of thinking about what I need to do (and it's also a good example of use of the e qualifier). I will leave your answer as the accepted one and will possibly revisit later. – Kusalananda Nov 15 '19 at 9:54
3

You don't need a loop. An intermediate variable is enough. Use the ^ parameter expansion sigil to distribute over the array.

dirpath=(*(N-/))
print -rC1 -- $^dirpath/*(.NOL[1,2])

Or you can use an anonymous function to avoid the temporary variable (which becomes $argv/$@ the anonymous function arguments):

() {print -rC1 -- $^@/*(.NOL[1,2])} *(N-/)
  • Thanking you graciously for this! – Kusalananda Nov 14 '19 at 17:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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