Sort the output of find -exec ls

Is it possible to the output of find … -exec ls -ls ; sorted alpabetically, by filename?

This is my cron command:

find /home/setefgge/public_html -type f -ctime -1 -exec ls -ls {} ;

This command works okay, for the most part. But the results are not sorted in any meaningful sequence. It would be very helpful if they would be sorted by the file name field.

Asked By: MaJ

ls -l $(find /home/setefgge/public_html -type f -ctime -1 | sort)
Answered By: Walter A

Why not pipe the result of find through sort and then execute ls for each of the lines?

find . -type f -ctime -1 | sort | while IFS= read -r filename; do ls -ls "$filename"; done
Answered By: groxxda

I assume that your file names don’t contain newlines.

find /home/setefgge/public_html -type f -ctime -1 -exec ls -nls {} + | sort -k 10

Using + instead of ; to terminate the -exec action makes it faster by batching the invocations of ls. You can sort by piping through the sort command; tell it to start sorting at the 10th field (the first 9 are the metadata: blocks, permissions, link count, user, group, size, and 3 date/time fields). The option -n tells ls to use numeric values for the user and group, which avoids the risk of user or group names containing whitespace.

Alternatively, with zsh, you can get away with no assumption on any name by using glob qualifiers to collect and sort the files and zargs to run ls multiple times if the command line would be too long. You do need GNU ls (specifically its -f option) to avoid re-sorting by ls (another approach would be to emulate ls with zsh’s zstat).

autoload -U zargs
zargs -- /home/setefgge/public_html/**/*(.c-2) -- ls -lnsf

POSIX has this to say about dates in an ls -long listing:

The <date and time> field shall contain the appropriate date and timestamp of when the file was last modified. In the POSIX locale, the field shall be the equivalent of the output of the following date command:

date "+%b %e %H:%M"

…if the file has been modified in the last six months, or:

date "+%b %e %Y"

Taking this into consideration, and ensuring that if there are any newlines in a filename they are properly globbed with the also POSIX specified ls -q option, it is relatively easy to prepare a regex for an ls result without find at all:

d=$(date "+%b %e") y=$(date --date=yesterday "+%b %e")
echo "$d" "$y"

Jul  5 Jul  4

grep for that and you will return only lines that contain the strings representing either today’s or yesterday’s dates. The following command adds to that a little:

ls -alRcq | sed "1H;/^-/!{/./d;N;h};/$d|$y/!d;x;/n/p;g"

ls options consist of:

  1. -a return all files in a directory – including those that begin with a .dot
  2. -l long listing
  3. -R recursively list all child directories
  4. -c display modification time rather than access time
  5. -q return the shell glob ? rather than non-printable or tab characters in a filename

Those results are passed over the |pipe file to sed which matches only:

  1. The blank line preceding a pathname and the following line
  2. Lines beginning with - (in other words – not d for directory) that also contain your date.
  3. It does not print the pathname lines though unless the directory they name actually contains files you’ve filtered for.

The output looks like this:

ls -alRcq --color=always | 
sed "1H;/^-/!{/./d;N;h};/$d|$y/!d;x;/n/p;g"

-rw------- 1 mikeserv mikeserv   2086 Jul  4 10:52 .bash_history
-rw------- 1 mikeserv mikeserv   2657 Jul  4 15:20 .lesshst
-rw-r--r-- 1 mikeserv mikeserv    681 Jul  5 05:18 .zdirs
-rw------- 1 mikeserv mikeserv 750583 Jul  5 08:28 .zsh_history
-rw-r--r-- 1 mikeserv mikeserv    166 Jul  4 23:02 Terminology.log
-rw-r--r-- 1 mikeserv mikeserv 433568 Jul  4 13:34 shot-2014-06-22_17-10-16.jpg
-rw-r--r-- 1 mikeserv mikeserv 445192 Jul  4 13:34 shot-2014-06-22_17-11-06.jpg

-rw------- 1 mikeserv mikeserv  37325 Jul  4 22:51 desktop_localhost_C.eet
-rw------- 1 mikeserv mikeserv  37325 Jul  4 23:30 desktop_localhost_en_US.eet
-rw------- 1 mikeserv mikeserv  24090 Jul  4 22:51 desktop_util_localhost_C.eet
-rw------- 1 mikeserv mikeserv  24090 Jul  4 23:30 desktop_util_localhost_en_US.eet
-rw------- 1 mikeserv mikeserv  16037 Jul  4 23:30 icon_themes_localhost.eet
-rw------- 1 mikeserv mikeserv   3117 Jul  4 23:30 icons___efreet_fallback_localhost.eet
-rw------- 1 mikeserv mikeserv 768039 Jul  4 23:30 icons_gnome_localhost.eet
-rw------- 1 mikeserv mikeserv  18589 Jul  4 23:30 icons_hicolor_localhost.eet

-rw-r--r-- 1 mikeserv mikeserv   30 Jul  4 19:10 pavucontrol.ini

-rw-r--r-- 1 mikeserv mikeserv 94332179 Jul  4 13:36 conf.tar.lz4.bak

Yes, it even works with LS_COLORS – which is probably a low priority for your cron of course, but, hey your options are open.

In any case this offers some significant advantages over some other possible solutions.

  1. In the first place find + ls involves multiple invocations – this only involves a single ls process, and this is why it is able to reliably sort everything – which it does by default – and so sort is also made ancillary.

  2. Any solution involving find and sort and ls is pretty much doing all of the work twice. ls and find will both resolve every pathname and stat every file. ls and sort will both sort all of the results. It is probably best instead to just use the single ls.

  3. Then of course there’s the date and sed portions of this answer. What’s important to note about that is you do the hard part and get the regex first – and only once – and afterward you only prune a single list of results rather than say, get results, get results, sort results and sort results.

  4. This does not break on filenames containing newlines, as other solutions likely will. This solution does have its own caveats – which I explain next – but they are minute and easily handled. In my opinion, this is the most robust solution here.

There are two cases in which the above command might cause you problems. The first involves the ? globs in the filenames – while as is it is already a more robust solution than any other offered here, and the likelihood that you will encounter a ? at all is small enough on its own, there is a possibility that resolving those globs could match more than one filename. Please see this for more information on this subject.

The other possibility involves a false positive – for instance if you have a filename actually matching the date string for which we are searching with grep but that was not actually modified on either of those days. I am not counting on that being an issue, but, if it is, ask about it and I can probably help you make the regex more specific in order to handle this.

Answered By: mikeserv

You can actually use a combination of find, xargs and ls.

Here is a sample command: find . -type f -print0 | xargs -0 ls -lt

  • find will recursively look for all the files under the current directory.
  • xargs will pass this list of files to the ls command in one single call (provided find returns less than ARG_MAX files).
  • ls -lt will sort these files by time and format the output

In order to retrieve your system ARG_MAX you can type:

$ getconf ARG_MAX
> 2621440
Answered By: Max
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.