7

I know that I can sort the output of ls by time with the -t option.

I know that when I have so many files that they don't fit in a single ls invocation, I can normally use xargs (or find ... -exec ... {} +) to let ls be called multiple times.

How can I combine the two? I have more files than fit on the command-line, and wish to list them sorted by time. find . -type f -exec ls -t {} + doesn't work, because supposing exactly 1000 file names fit on the command-line, and 3000 files are present, this will run ls -t [first 1000 files]; ls -t [second 1000 files]; ls -t [last 1000 files], where the last 1000 files find sees may well have a modification time before any of the first 1000. It doesn't seem like anything involving xargs or equivalent has any chance whatsoever of working, it seems like that approach is fundamentally flawed, but I cannot find a way that does work.

3 Answers 3

14

ls -t on its own will list all files in the current directory with that sorting, without ever needing to list them on the command line at all.

If you need the recursion behaviour of find, or to do some other tests on the files, you can have find generate timestamped entries, either through stat or through GNU find's -printf option and sort it. Something like:

find . -type f -printf '%T@ %p\0' | sort -zn

-printf '%T@ %p\0' generates null-separated Unix timestamp (%T@)-filename (%p) pairs. sort -z is also a non-standard GNU extension, which uses null-delimited records to be filename-safe. The sort option is supported in most of the BSDs too, but -printf is GNU-only as far as I know.

You can cut that output back into filenames only, or any other format you like.

4
  • Thanks! I didn't know -printf was this powerful. According to the documentation, %A@ is the access time. I'm looking for the mtime, so I'd need %T@.
    – hvd
    Commented Aug 11, 2014 at 7:43
  • It's once again astonishing what seemingly simple tools have in stock... Commented Aug 11, 2014 at 8:07
  • What command would you use to cut each of the NUL delimited strings after the first space?
    – kasperd
    Commented Aug 11, 2014 at 17:27
  • 1
    @kasperd, (GNU) sed -z 's/^[^ ]* //' or tr '\0\n' '\n\0' | cut -d ' ' -f2- | tr '\0\n' '\n\0' Commented Aug 12, 2014 at 8:38
3

You can use find with perl Posixly:

$ find ! -name . -prune -print | perl -lne '
    $h{$_} = -M;
    END { print for sort {$h{$a} <=> $h{$b}} keys %h }
'

This assume that you don't have newline character in your filename.

To handle newline, you can:

$ find ! -name . -prune -exec printf "%s\0" {} + | perl -0lne ...

or use perl -le then split /\0/,<> in your code.

10
  • Oh, nice. Although I don't have newline characters in file names, it can be made to handle them (and still valid for any POSIX find, I think, although the use of perl is inherently somewhat non-portable) by adjusting it to find ... -exec printf "%s\0" {} + | perl -0lne '...', optionally also modifying the perl bit to print newlines instead of NULs, or act on the files directly.
    – hvd
    Commented Aug 11, 2014 at 10:14
  • @hvd: -printf is not POSIX. perl is more portable, always.
    – cuonglm
    Commented Aug 11, 2014 at 10:16
  • I know, that's why I didn't use -printf. I used -exec in combination with POSIX's printf utility.
    – hvd
    Commented Aug 11, 2014 at 10:18
  • I was referring to my comment here, where I didn't use %T@. %T@ won't work with the printf utility, only with find's non-portable -printf option.
    – hvd
    Commented Aug 11, 2014 at 10:21
  • @hvd: If you use that, you acn use perl -le, then split /\0/,<> to get list of all files.
    – cuonglm
    Commented Aug 11, 2014 at 10:22
3

In zsh:

print -rl -- *(om)

The glob qualifier om sorts files by modification time (reverse chronological order, i.e. newest first, like ls -t). Use Om to get the opposite order (chronological order, like ls -tr). Make this *(Dom) to include dot files.

You can of course vary the pattern, for example **/*(Dom) to recurse into subdirectories. If you want to use multiple patterns, put them in parentheses and use the | operator to separate them, e.g. *.(png|jpg)(om).

Since print is a shell builtin, this command is not affected by the maximum command line length limit. If you want to call an external command to act on the files in batches and in order, you'll run into the length limit. You can use printf '%s\0' *(om) | xargs -0 somecommand to feed the files to xargs if your xargs supports null-separated input (Linux, *BSD), or zsh's own zargs:

autoload -U zargs
zargs -r -e '' -- *(om) '' somecommand

To process one file at a time, use a simple loop, which zsh lets you abbreviate (csh-style):

for x (*(om)) somecommand
2
  • This ignores the fact that I was searching files in all subdirectories. Does zsh have anything like bash's globstar option to address that? A quick search suggests **/*, but I don't have zsh installed to verify.
    – hvd
    Commented Aug 12, 2014 at 5:15
  • 1
    @hvd, the **/* syntax was invented by zsh in the early 90s, decades before bash copied (a subset of) it. See The result of ls * , ls ** and ls *** for more historical details of that globbing operator. zsh 1991, ksh93 2003, fish 2005, bash 2009, tcsh 2010. Commented Aug 12, 2014 at 6:26

You must log in to answer this question.

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