tags:

views:

236

answers:

5

Imagine I've got the following in a text file opened under Emacs:

some    34
word    30
another 38
thing   59
to      39
say     10
here    47

and I want to turn into this, adding 1 to every number made of 2 digits:

some    35
word    31
another 39
thing   60
to      40
say     11
here    48

(this is a short example, my actual need is on a much bigger list, not my call)

How can I do this from Emacs?

I don't mind calling some external Perl/sed/whatever magic as long as the call is made directly from Emacs and operates only on the marked region I want.

How would you automate this from Emacs?

I think the answer I'm thinking of consist in calling shell-command-on-region and replace the region by the output... But I'm not sure as to how to concretely do this.

A: 

Moderately tested; use M-: and issue the following command:

(while (re-search-forward "\\<[0-9][0-9]\\>" nil t) (let ((x (match-string 0))) (delete-backward-char 2) (insert (format "%d" (1+ (string-to-int x))))))
doublep
+1  A: 

It doesn't protect against 99->100.

(defun add-1-to-2-digits (b e)
  "add 1 to every 2 digit number in the region"
  (interactive "r")
  (goto-char b)
  (while (re-search-forward "\\b[0-9][0-9]\\b" e t)
    (replace-match (number-to-string (+ 1 (string-to-int (match-string 0)))))))

Oh, and it operates on the region. If you want the entire file, then you replace b and e with (point-min) and nil.

Trey Jackson
+16  A: 

This can be solved by using the command query-replace-regexp (bound to C-M-%):

C-M-% \b[0-9][0-9]\b return \,(1+ \#&)

The expression that follows \, would be evaluated as a Lisp expression, the result of which used as the replacement string. In the Lisp expression, \#& would be replaced by the matched string, interpreted as a number.

By default, this works on the whole document, starting from the cursor. To have this work on the region, there are several posibilities:

  1. If transient-mark-mode is turned on, you just need to select the region normally (using point and mark);
  2. If for some reason you don't like transient-mark-mode, you may use narrow-to-region to restrict the changes to a specific region: select a region using point and mark, C-x n n to narrow, perform query-replace-regexp as described above, and finally C-x n w to widen. (Thanks to Justin Smith for this hint.)
  3. Use the mouse to select the region.

See section Regexp Replacement of the Emacs Manual for more details.

huaiyuan
very nice, now I'll have to see what it does... Can't mod you up: no more votes today (I've done it differently too, but I prefer your version, see my own answer).
Webinator
Webinator
This works - with literally the text provided. Your cursor has to be before the text you want replaced though, so do M-< to go to beginning of buffer first.
Justin Smith
@WizardOfOdds: It works for me. :] Which version of Emacs are you using? Any error message in the echo area/minibuffer?@Justin Smith: I just tried, and it seems as long as a region is active (being selected), then it doesn't matter whether the cursor is at the end. But you are right that when no region were selected, then the replacements would only happen between the cursor and the end of buffer.
huaiyuan
Webinator
@but I can't make it work on the *region* I have: it replace everything from the cursor up until the end of my documents, which is not what I want. Can I do a "replace-regexp" on just a marked region?
Webinator
from the link huaiyuan posted at the bottom of his answer: "You can use Lisp expressions to calculate parts of the replacement string. To do this, write ‘\,’ followed by the expression in the replacement string." In other words you can put any valid lisp expression in a regexp replacement and it will be replaced with its value.
Justin Smith
regarding the region question: it does the right thing if I hilight the region with the mouse - my guess is you need to figure out what the difference is between highlighting with your mouse and just using mark and point to define region -- or maybe your answer is just that you have to select the region with the mouse first.
Justin Smith
@JUstin Smith: oh gotcha, I'm using replace-regexp all the time but with trivial substitution... I didn't realize this was actually a Lisp expression (hence my serious confusion) : )
Webinator
OK, by default it works from cursor to end of document, I have to use the Transient Mark mode to work on the selection.
Webinator
Another option I just thought of: M-x narrow-to-region which makes everything outside the region out of bounds for edits. M-x widen undoes the narrowing - keybindings are C-x n n and C-x n w
Justin Smith
@Justin Smith: excellent too... Now I got it all working and I edited huaiyuan's answer to be a bit more verbose : )
Webinator
Sorry for the confusion; I have transient-mark-mode turned on, and forgot it's off by default.
huaiyuan
@WizardOfOdds: Thanks for the edit; I cleaned up the answer a little bit more.
huaiyuan
A: 

I managed to get it working in a different way using the following (my awk-fu ain't strong so it probably can be done in a simpler way):

C-u M-x shell-command-on-region RET awk '$2>=0&&$2<=99 {$2++} {print}' RET

but I lost my indentation in the process : )

Seeing all these answers, I can't help but have a lot of respect for Emacs...

Webinator
+5  A: 

Emacs' column editing mode is what you need.

  • Activate it typing M-x cua-mode.

  • Go to the beginning of the rectangle (leave cursor on character 3) and press C-RET.

  • Go to the end of the rectangle (leave cursor on character 7). You will be operating on the highlighted region.

  • Now press M-i which increments all values in the region.

You're done.alt text

Török Gábor