2

In searching how to make find sort the files it finds based on modification time I found this answer which provides the following incantation:

find . -type f -printf '%T@ %p\n' | sort -k 1nr | sed 's/^[^ ]* //' | head -n 10

The problem with the above is that I can't memorize it and I can't figure a way to package it as a shell script so that I can feed it with my own invocation of find where I 'll have the option of applying whatever arguments I want. E.g. how can I package the above in the form of a shell script "showLatest" so I can do things like the following?

find src -type f -iname \*.py | showLatest 10
Marcus Junius Brutus
  • 4,587
  • 11
  • 44
  • 65
  • 1
    I think it is not possible in this form, because the only way the second command in the pipe can sort the resulting lines is based on the content it get, so the find should be modified to provide the timestamps. Unless you think that the showLatest should reconstruct the timestamps from filenames, that is very ugly and time comsuming. – enzotib Nov 20 '13 at 20:09

3 Answers3

3

In zsh, showLatest 10 is (om[1,10]) at the end of the pattern. These are glob qualifiers; om sorts by modification time (youngest first) and [1,10] selects the first 10 matches.

find src -type f -iname \*.py is print -lr src/**/(#i)*.py(D.) (you need to run setopt extended_glob first, put that in your ~/.zshrc).

The globbing flag . filters regular files only, D includes hidden files and files in hidden directories like the find version would. (#i) is a globbing flag that makes the remainder of the pattern case-insensitive.

print -lr prints its arguments, one per line (make that print -lr -- if the first argument may begin with a dash). Combining the two, to get the most recent files:

print -lr src/**/(#i)*.py(D.om[1,10])

which in practice you can shorten to print -lr src/**/*.py(om[1,10]).

In zsh, you probably won't need this because you can sort the list of files when you get it, at least if they're all coming from a single pattern. Here's a way to implement showLatest in zsh that works with an arbitrary list of files. Unfortunately, the om glob qualifier can only be applied to a single pattern, and a single pattern can only match files in a single directory or tree. A trick to bypass this is to use the e (or +) glob qualifier to inject an arbitrary list into a match result. Then apply an o glob qualifier to perform the sorting; built-in qualifiers don't act on the result of the e/+ rewrite, but custom ones (oe or o+) do (as of zsh 5.0.5).

#!/usr/bin/env zsh
zmodload zsh/stat
files=("${(@f)$(<&0)}")
print -lr .(e\''reply=($files)'\'noe\''stat -A REPLY +mtime -- $REPLY'\')
  • Your showLatest script won't work unless all the files are in the current directory. ((foo|bar)(om[1,1]) is a valid glob, (/etc/passwd|/bin/ls)(om[1,1]) or even (./foo|./bar)(om[1,1]) are not. – Stéphane Chazelas Oct 21 '15 at 08:52
  • @StéphaneChazelas Good point. Is there anything simpler than .(e\''reply=($files)'\'om) to sort an arbitrary array of files via a glob qualifier? – Gilles 'SO- stop being evil' Oct 21 '15 at 12:34
  • Could have been a nice trick, but .(e\''reply=($files)'\'om) doesn't work for me. I can't think of a straightforward way to do it. – Stéphane Chazelas Oct 21 '15 at 15:42
  • @StéphaneChazelas My bad, applying almost any glob qualifier seems to simply sort in reverse, probably a side effect of how sorting is implemented. But oe does sort on the output of e, at least as of zsh 5.0.5. Annoying but doable. – Gilles 'SO- stop being evil' Oct 21 '15 at 17:30
1

This script works for me:

#!/bin/bash
#
while IFS= read -r line; do
  find "$line" -printf '%T@ %p\n'
done | sort -k 1nr | sed 's/^[^ ]* //' | head -n 10

you can pipe your find through it:

(0)asus-romano:~/tmp% find . -type f | ./script11.sh
./script11.sh
./script10.sh
[...]

In zshyou could used a global alias, which is much faster, but I think that bashdoes not have them:

(0)asus-romano:~/tmp% alias -g showlast10="-printf '%T@ %p\n' | sort -k 1nr | sed 's/^[^ ]* //' | head -n 10"
(0)asus-romano:~/tmp% find . -type f showlast10
./script11.sh
./script10.sh
./Just  tab.xyz
[...]
Rmano
  • 3,425
1

First of, it is certainly possible to write a script like showLatest, probably even using find in it. But then every file would have to be looked at twice.

So although this does not answer the question asked in the end, it should solve the problem described first. Namely, how to feed a flexible parameter list to find.

sfind.sh:

#!/bin/bash
find "${@:1:$[$#-1]}" -printf '%T@ %p\n' | sort -k 1nr | sed 's/^[^ ]* //' | head -n ${@:$#:1}

The general invocation is sfind.sh [find-parameters] count. For the given example it would be sfind.sh src -type f -iname \*.py 10

Explanation:

  • $@ contains the parameter list passed to the script
  • $# is the length of $@
  • ${@:1:$[$#-1]} all parameters except the last one
  • ${@:$#:1} the last parameter
Adaephon
  • 4,456