10

In Python I'd do the following to process a file line by line:

with open(infile) as f:
    for line in f:
        process(line)

Trying to look up how to do the same in elisp (with buffers instead of files), I found no obvious way.

(What I want to end up with is two ordered datastructures of lines, one with all the lines matching a regex, the other containing those that did not match.)

21

There are various ways to do it. Kaushal's way can be made a good bit more efficient, with:

(goto-char (point-min))
(while (not (eobp))
  (let ((line (buffer-substring (point)
                                (progn (forward-line 1) (point)))))
    ...))

But in Emacs it is much more customary to work on the buffer rather than on strings. So rather than extract the string and then work on it, you'd just do:

(goto-char (point-min))
(while (not (eobp))
  ...
  (forward-line 1))

Also, if you want to operate on a region rather than on the whole buffer, and if your "operate" includes modifying the buffer, it's frequent to do it backwards (so that you don't get bitten by the fact that the "end" position of your region moves every time you modify the buffer):

(goto-char end)
(while (> (point) start)
  ...
  (forward-line -1))
  • Thanks for those optimization tips! Always good to learn from you. – Kaushal Modi Jan 14 '16 at 15:29
  • About the last snippet, should it be this way: (let ((start (point))) (goto-char (point-max)) (while (> (point) start) ... (forward-line -1)))? – Kaushal Modi Jan 14 '16 at 16:21
  • No, the last snippet just assumes that start and end are existing variables which delimit the region on which we want to operate. – Stefan Jan 14 '16 at 19:41
6

I don't know of any idiomatic way but I came up with this:

(defun my/walk-line-by-line ()
  "Process each line in the buffer one by one."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (not (eobp))
      (let* ((lb (line-beginning-position))
             (le (line-end-position))
             (ln (buffer-substring-no-properties lb le)))
        (message ">> %s" ln) ; Replace this with any processing function you like
        (forward-line 1)))))
1

I think the following is as idiomatic as it can get:

(dolist (line (split-string (buffer-string) "\n")) 
  ... process line here ...
  )

EDIT: Here is another solution with loop in place of dolist, and which also classifies the lines according to whether or not they match your regular expression:

(loop for line in (split-string (buffer-string) "\n")
  if (string-match "your-regexp" line)
    collect line into matching
  else
    collect line into nonmatching
  finally return (cons matching nonmatching)
  )

If you set a variable to the output of this function, say (setq x (loop ...)), then the desired list of matching lines will be found in (car x), with the list of nonmatching lines being (cdr x).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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