6
[
  {
    "id": 0,
    "finished": 1,
    "orgn": 17
  },
  {
    "id": 1,
    "finished": 2,
    "orgn": 17
  },
  {
    "id": 2,
    "finished": 3,
    "orgn": 17
  },
  {
    "id": 3,
    "finished": 4,
    "orgn": 17
  }
]

I need to increment every field finished by 1

The result must be:

[
  {
    "id": 0,
    "finished": 2,
    "orgn": 17
  },
  {
    "id": 1,
    "finished": 3,
    "orgn": 17
  },
  {
    "id": 2,
    "finished": 4,
    "orgn": 17
  },
  {
    "id": 3,
    "finished": 5,
    "orgn": 17
  }
]
Tobias
  • 32,569
  • 1
  • 34
  • 75
a_subscriber
  • 3,854
  • 1
  • 17
  • 47
  • This sounds like a duplicate, but I don't have time now to look for it. – Drew Nov 14 '19 at 20:36
  • 1
    Possible duplicate of [How increment specific value in text](https://emacs.stackexchange.com/questions/53736/how-increment-specific-value-in-text) – Drew Nov 14 '19 at 20:37
  • 1
    @Drew It may be a duplicate but not of that one: here the goal is to replace each N with N+1, there the goal is to replace each site with increasing values. – Gilles 'SO- stop being evil' Nov 14 '19 at 21:20

3 Answers3

9

Use the following key sequence:

M-C-% \("finished":[[:space:]]*\)\([0-9]+\) RET
\1\,(1+ (string-to-number \2))RET

  • M-C-% is bound to query-replace-regexp.
  • \("finished":[[:space:]]*\)\([0-9]+\) is the regexp to search for, the first group is just for copying into the replacement string, the second group is the number to be transformed.
  • \1\,(1+ (string-to-number \2)) is the replacement string (\1\,(1+ (read \2)) would also work).
    • \1 copies group 1 at the head of the search string.
    • \,(...) evaluates the form (...) and inserts the result at the position of the escape sequence.
    • The Elisp transforms the string from group 2 (i.e., the string with the number) into a number and adds 1. The result is automatically translated back to a string that is inserted into the replacement string.
Tobias
  • 32,569
  • 1
  • 34
  • 75
  • There's a bit of syntactic sugar you can use to simplify the replacement: you can substitute `\#2` for either `(string-to-number \2)` or `(read \2)`. So the whole replacement becomes `\1\,(1+ \#2))`. – Phil Hudson Nov 19 '19 at 22:55
2

I'm going to use the function found here.

(defun increment-number-at-point ()
  (interactive)
  (skip-chars-backward "0-9")
  (or (looking-at "[0-9]+")
      (error "No number at point"))
  (replace-match (number-to-string (1+ (string-to-number (match-string 0))))))

For a quick solution, I would use a macro. This is appropriate if you only need to do it a small number of times, and the number of replacements is small.

First, go to the beginning of the file: M-S-<.

Now, we can record the macro. I'm going to search forward for the next instance of "finished", go to the end of the line, go back two characters, then run the command increment-number-at-point.

C-x ( C-s finished C-e C-b C-b M-x increment-number-at-point RET C-x e.

You can then keep pressing e to repeat this macro until you get to the end of the file.

zck
  • 8,984
  • 2
  • 31
  • 65
1

Not everything needs to be done in pure elisp or using just Emacs facilities. In this case, I would use shell-command-on-region (usually bound to M-|): first mark the region of interest and then type M-| and pass the following shell command to it (followed by RET):

awk -F ":" '/finished/ {n = $2+1; printf "%s: %s,\n", $1, n; next;} {print $0;}'

In words: use a colon as a field separator, for each line, if the line matches finished, increment the second field and print out the modified line, then go immediately to the next line; otherwise, just print the line. There are probably shorter ways to do it, and perhaps some error checking needs to be added (e.g. if the second field on finished lines is missing or not a number), but in the case you showed, this should be enough.

Running shell-command-on-region with that command will produce output in a separate buffer, where you can check and make sure that it did the right thing. Once you are sure it is doing the right thing, you can replace the original region with the modified text using a C-u prefix on shell-command-on-region:

C-u M-| awk -F ":" '/finished/ {n = $2+1; printf "%s: %s,\n", $1, n; next;} {print $0;}' RET

shell-command-on-region allows you to use all sorts of tools outside Emacs to do text processing, so IMO it is an indispensable tool in one's tool box.

NickD
  • 27,023
  • 3
  • 23
  • 42