Vim :s replace specific N < g occurrences on a line

Viewed 161

i am try to use a comma (,) to separate the thousands in the first two numeric fields using vim command

%s/\([0-9]\)\([0-9]\)\([0-9]\)\([0-9]\);/\1,\2\3\4;/g

but in this case it gonna add comma also to 9,995 in the second line , what can i use to replace specific N < g occurrences .

input

BitstreamCyberCJK;Freeware;30275;28686;v2.0 ;beta (1998-03-17)
Y.OzFontN;Freeware;21957;7621;v13.00 sfnt rev 9995; Pen-Ji (2010-08-24)

expected output

 BitstreamCyberCJK;Freeware;30,275;28,686;v2.0 ;beta (1998-03-17)
 Y.OzFontN;Freeware;21,957;7,621;v13.00 sfnt rev 9995; Pen-Ji (2010-08-24)
4 Answers

There is a way to repeat the last command: @: also, you could specify the number of repeats, for example: 10@:.

So, start from replacing only first match: %s/\([0-9]\)\([0-9]\)\([0-9]\)\([0-9]\);/\1,\2\3\4;/

Then, as we already have done one of N substitution, repeat it N-1 times.

For example, to replace the first 10 numbers, use:

%s/\([0-9]\)\([0-9]\)\([0-9]\)\([0-9]\);/\1,\2\3\4;/
9@:

I would use a shorter and more manageable search pattern:

:%s/\(\d\)\(\d\{3}\);/\1,\2;/g

Then, I would drop the /g flag to only substitute the first match on each line, since the goal is specifically not to substitute all matches on all lines:

:%s/\(\d\)\(\d\{3}\);/\1,\2;

Then, I would repeat the last substitution on every line:

g&

See :help \d, :help \{, :help :s_flags, and :help g&.

Firstly I would advise you to clean up your regex:

:%s/\([0-9]\)\([0-9]\{3}\);/\1,\2;/g

Secondly the g flag in the end makes vim replace all occurrences. Without the flag, it will just replace the first one. Since % runs the substitution on each line, %s//g will replace the first occurrence on every line.

Since you want to replace the first TWO occurrences you could just run it twice. However we want to be a bit elegant here. There is the :& command to repeat the last substitution. We can even chain them together:

:%s/\([0-9]\)\([0-9]\{3}\);/\1,\2;/ | norm %&

Update: The other solutions can be used the same way:

:%s/\([0-9]\)\([0-9]\{3}\);/\1,\2;/ | norm g&

or

:%s/\([0-9]\)\([0-9]\{3}\);/\1,\2;/ | norm :@

There are already good answers above, but I'll share how I'd approach it. (Because I don't want to count how many is "N", and I don't want to repeat the substitutions only to find I did one too many, etc.)

I would use one of the substitutions listed above, like :%s/\(\d\)\(\d\{3}\);/\1,\2;/g. Then I'd run a second substitution, searching for "rev " and removing a comma if it exists:

%s/\(rev \d\),\(\d\{3\};\)/\1\2/g
%                               g  all lines, and all occurences on a line
 s/                       /    /   substitute
   \(      \) \(        \)         backref capture groups
     rev \d                        text "rev " and a digit (backref 1)
             ,                     comma
                \d\{3\};           three digits and a semicolon (backref 2)
                           \1\2    backrefs 1 and 2

Some things to note --

  • If you know the "rev " appears only once on a line, then you can omit the g at the end of the substitution.
  • If somehow "rev " appears in other contexts in the file -- maybe in text like "Foo bar abbrev 4,321" -- then you'd want to account for that.
  • If there are no matches of "rev " followed by a digit and a comma, then Vim will give a "pattern not found" message.

Also, this searches for a four-digit number with a comma. If there's a chance of five-digits or 6-digits numbers where you'd want to remove the comma, then you'd use this, which is almost the same:

%s/\(rev \d\{1,3\}\),\(\d\{3\}\);/\1\2/g
         \d\{1,3\}  ,                     1 to 3 digits followed by a comma
Related