1

I'd like to write a bit of elisp to mark all files in a dired buffer that are "old": the files were last modified more than 10 days ago, say.

What would be a good way to do that? It seems there are functions to mark files in a dired buffer based on their name, using a regexp or whatever, but I don't see anything that handles file metadata other than the name.

4 Answers 4

2

I don't see anything that handles file metadata other than the name.

That's exactly the purpose of command dired-mark-sexp. Use a Lisp sexp to match files, where the sexp can use (pseudo-)variables whose values are file metadata values.

You can use the command interactively or use it or some of its code from Lisp.

You can use (pseudo-)variable time in the sexp you use for marking -- compare some given time with the value of time for each file.

Here's the doc string. (This is the doc for the version from Dired+, which enhances the vanilla version in relatively minor ways -- you can use the vanilla version for what you want just as easily.)

dired-mark-sexp is an interactive compiled Lisp function in dired+.el.

It is bound to M-(, * (, menu-bar mark marks-mark marks-mark-sexp.

(dired-mark-sexp PREDICATE &optional UNMARK-P)

Mark files for which PREDICATE returns non-nil.

With a prefix arg, unmark or unflag those files instead.

PREDICATE is a lisp sexp that can refer to the following symbols as variables:

  • mode [string] file permission bits, e.g. "-rw-r--r--"
  • nlink [integer] number of links to file
  • size [integer] file size in bytes
  • uid [string] owner
  • gid [string] group (If the gid is not displayed by ls, this will still be set (to the same as uid))
  • time [string] the time that ls displays, e.g. "Feb 12 14:17"
  • name [string] the name of the file
  • sym [string] if file is a symbolic link, the linked-to name, else ""
  • inode [integer] the inode of the file (only for ls -i output)
  • blks [integer] the size of the file for ls -s output (ususally in blocks or, with -k, in Kbytes)

Examples:

  • Mark zero-length files: `(equal 0 size)'

  • Mark files last modified on Feb 2: `(string-match "Feb 2" time)'

  • Mark uncompiled Emacs Lisp files (.el' file without a .elc' file):

    First, Dired just the source files: dired *.el.

    Then, use M-( with this sexp:

      `(not (file-exists-p (concat name "c")))`
    

There's an ambiguity when a single integer not followed by a unit prefix precedes the file mode: It is then parsed as inode number and not as block size (this always works for GNU coreutils ls).

Another limitation is that the uid field is needed for the function to work correctly. In particular, the field is not present for some values of ls-lisp-emulation.

This function operates only on the Dired buffer content. It does not refer at all to the underlying file system. Contrast this with find-dired, which might be preferable for the task at hand.

2
  • Oops, I misread sexp as regexp and didn't notice it -- thanks for pointing that out. Still, the time variable is a little disappointing, since it's the displayed string; I was hoping for something structured -- either seconds since the epoch, or at least a consistently-formatted string. For the full general functionality I wanted, I'd need code that parses all those time strings into a better format. But there is a decent heuristic that I'll post as another answer.
    – Dan Drake
    Oct 10, 2021 at 17:51
  • 1
    Right, these pseudo-variables all involve text shown in the buffer. If you want a time object then you have to use Elisp functions to give it to you.
    – Drew
    Oct 11, 2021 at 1:11
1

If you use libraryfind-dired+.el then you can use command find-time-dired. (You can also call it from Lisp or use part of its code, if you prefer.)

It puts only the selected files in a Dired buffer. You can then mark them all (or some subset).

Here's the doc string:

find-time-dired is an interactive Lisp function in find-dired+.el().

(find-time-dired DIR MIN-TIME MAX-TIME
                 &optional DEPTH-LIMITS EXCLUDED-PATHS)

Find files in directory DIR newer or older than a timestamp.

The output is shown in a Dired buffer.

  • MIN-TIME is a format-time string parsable by parse-time-string, such as "2014-12-25 23:59:00". Only files newer than this are shown. If MIN-TIME is nil or a string matching regexp "^\s-*$", there is no lower time limit.

  • MAX-TIME is also a format-time string parsable by parse-time-string. Only files older than this time are shown. If MAX-TIME is nil or a string matching regexp "^\s-*$", the upper time limit is the current system time.

Optional arg DEPTH-LIMITS is a list (MIN-DEPTH MAX-DEPTH) of the minimum and maximum depths. If nil, search directory tree under DIR.

Optional arg EXCLUDED-PATHS is a list of strings that match paths to exclude from the search. If nil, search all directories.

If args DEPTH-LIMITS and EXCLUDED-PATHS are both non-nil then the command run is essentially the following:

 find . -mindepth MIN-DEPTH -maxdepth MAX-DEPTH
     \( -path EXCLUDE1 -o -path EXCLUDE2 ... \)
     -prune -o \( -TIME-SWITCH -SINCE-MIN -TIME-SWITCH +SINCE-MAX \)
     LS-SWITCHES

where:

  • EXCLUDE1, EXCLUDE2... are the EXCLUDED-PATHS, but shell-quoted.
  • TIME-SWITCH is find-diredp-time-prefix' concatenated with "min"`.
  • SINCE-MIN is the elapsed time since MIN-TIME in minutes.
  • SINCE-MAX is the elapsed time since MAX-TIME in minutes.
  • LS-SWITCHES is (car find-ls-option).
0

Working off of Drew's answer with the vanilla dired-mark-sexp, something that works in full generality is pretty tricky, since the time variable is the formatted date/time string, and I'd need to parse all the different display formats into something structured: seconds since the epoch, or some other easily-parseable data structure.

There's probably a pretty good library or example code out there for that, but my use-case has a pretty simple heuristic that will work: look for times that don't have a colon in them.

The default dired / ls format appears to switch from showing the time to just showing a date for files that are six months old. That's good enough for me, so one answer to my question is: if your definition of "old" is "six months ago / whenever ls stops displaying times", then this will work:

Call dired-mark-sexp and for the predicate, use (not (string-match-p ":" time).

1
  • Note that the various find options above are probably a better solution for the kind of interactive use I was thinking of.
    – Dan Drake
    Oct 10, 2021 at 18:08
0

Another option, and probably the most direct and best answer to my specific question, is find-dired with an argument of -ctime +NDAYS. Then in the resulting dired buffer, do * t to mark all the files (presuming you haven't marked any yet; * t toggles the marked state of all the files in the dired buffer.)

The only problem for me is that I'm using emacs on Windows and I'm not sure if I have a working version of find.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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