Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question

3 Answers 3

up vote 8 down vote accepted

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.

share|improve this answer
    
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 Aug 11 at 7:43
    
It's once again astonishing what seemingly simple tools have in stock... –  countermode Aug 11 at 8:07
    
What command would you use to cut each of the NUL delimited strings after the first space? –  kasperd Aug 11 at 17:27
1  
@kasperd, (GNU) sed -z 's/^[^ ]* //' or tr '\0\n' '\n\0' | cut -d ' ' -f2- | tr '\0\n' '\n\0' –  Stéphane Chazelas Aug 12 at 8:38

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.

share|improve this answer
    
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 Aug 11 at 10:14
    
@hvd: -printf is not POSIX. perl is more portable, always. –  cuonglm Aug 11 at 10:16
    
I know, that's why I didn't use -printf. I used -exec in combination with POSIX's printf utility. –  hvd Aug 11 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 Aug 11 at 10:21
    
@hvd: If you use that, you acn use perl -le, then split /\0/,<> to get list of all files. –  cuonglm Aug 11 at 10:22

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
share|improve this answer
    
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 Aug 12 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. –  Stéphane Chazelas Aug 12 at 6:26
    
Thanks for the history lesson :) –  hvd Aug 12 at 7:02

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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