5

I would like to be able to return the total no. of lines for a set of marked files in dired.

i.e. if I marked 3 text files:

  • file 1 contains 100 lines
  • file 2 contains 50 lines
  • file 3 contains 60 lines

result would be 210 lines

Any ideas on how one might achieve this?

3 Answers 3

3

Tyler already gave a nice simple answer. But that answer depends on external tools like cat and wc.

I present an alternative Elisp based solution. For evaluation purposes you can put the following lisp code into your *scratch* buffer and call M-x evaluate-buffer on it.

If you want to install it copy it into your init file and evaluate it.

When dired-reduce-over-marked-files is defined go to the dired buffer mark the files where you want to count the lines and execute
M-x dired-reduce-over-marked-files RET (count-lines (point-min) (point-max)) RET.

(require 'subr-x)
(cl-defun dired-reduce-over-marked-files (fun
                                          &rest args
                                          &key
                                          (reduction #'+)
                                          (init 0)
                                          &allow-other-keys)
  "Apply FUN to each marked readable regular file in the dired buffer.
Reduce the results by applying REDUCTION with start value INIT.
REDUCTION defaults to #'+ and INIT defaults to 0.
They can be changed with keyword arguments :reduction and :init.
FUN can be an expression or a function.
If FUN is a function apply ARGS to it otherwise ARGS are ignored.
Interactively ARGS are read from minibuffer up to empty input.
ARGS are evaluated in the temporary file buffer.
For an example quoted arguments '(point-min) and '(point-max) evaluate
to the limits of point in the marked files.
If keywords :reduction or :init are given they must preceed the other arguments."
  (interactive
   (let ((keyword-args (equal current-prefix-arg '(4)))
         (fun (read-from-minibuffer "Fun or Function:" nil nil t 'read-expression-history)))
     (append
      (list fun)
      (list
       :reduction
       (or (and keyword-args
                (read-from-minibuffer "Reduction Function:" nil nil t 'read-expression-history))
           #'+)
       :init
       (or (and keyword-args
                (read-from-minibuffer "Start Value:" nil nil t 'read-expression-history)
                )
           0))
      (when (functionp fun)
        (cl-loop
         with str
         until (string-empty-p (setq str (read-from-minibuffer "Argument (finish with empty input):")))
         collect (read str))
        ))))
  (while (memq (car args) '(:reduction :init))
    (setq args (cddr args)))
  (save-excursion
    (dired-map-over-marks
     (let ((file (dired-file-name-at-point)))
       (when (and (file-regular-p file)
                  (file-readable-p file))
         (with-temp-buffer
           (insert-file-contents file)
           (setq init
                 (funcall
                  reduction
                  (if (functionp fun)
                      (apply fun (mapcar #'eval args))
                    (eval fun))
                  init)))))
     nil))
  (when (called-interactively-p 'any)
    (message "Result: %s" init))
  init)

You can use dired-reduce-over-marked-files for other purposes. For an instance you can substitute count-lines by count-words with the obvious effect. Or you can call count-matches to determine the number of matches for a specific regexp.

6

With the files marked, you can call a shell command on them with the ! key. When prompted, you can use the * key to indicate where you want all the marked files to appear. To get the line count of all the files, you want to pass them to wc as a single file, which you can create temporarily via cat. Which gives us:

  • mark all files
  • !
  • enter cat * | wc -l

Which will produce the output

210

Alternatively, you can drop cat, and just do:

  • wc -l *

for the last step, which will produce the output

  100 file1
   50 file2
   60 file3
  210 Total

Note that wc -l without the * won't work, as that will just give you the line counts for each file, not all three together.

1

A less elegant solution to Tobias's solution:

mark all the files in the dired buffer, then interactively evaluate this in that buffer (i.e. M-:)

(apply '+ (dired-map-over-marks
           (progn
             (let ((f (dired-file-name-at-point)))
               (with-temp-buffer
             (insert-file-contents f)
             (let ((c (count-lines (point-min) (point-max))))
               (message "%s  %s" f c)
               c))))
            nil))
1
  • 1
    Please re-indent your code. You've probably included TAB characters, which don't produce the right indentation.
    – Drew
    Aug 25, 2020 at 21:42

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.