wheelbarrow
wheelbarrow

Reputation: 335

vim: substitute specific character, but only after nth occurance

I need to make this exercise about regexes and text manipulation in vim.

So I have this file about the most scoring soccer players in history, with 50 entries looking like this:

1 Cristiano Ronaldo Portugal 88 121 0.73 03 Manchester United Real Madrid

The whitespaces between the fields are tabs (\t)

The fields each respond to a differen category: etc... This last field contains one or more clubs the player has played in. (so not a fixed number of clubs)

The question: replace all tabs with a ';', except for the last field, where the clubs need to be seperated by a ','.

So I thought: I just replace all of them with a comma, and then I replace the first 7 commas with a semicolon. But how do you do that? Everything - from regex to vim commands - is allowed.

The first part is easy: :2,$s/\t/,/g But the second part, I can't seem to figure out.

Any help would be greatly appreciated.

Thanks, Zeno

Upvotes: 2

Views: 461

Answers (5)

Randy Morris
Randy Morris

Reputation: 40927

This answer is similar to @Amadan's, but it makes use of the ability to provide an expression as the replace string to actually do the difficult bit of changing the first set of tabs to semicolons:

%s/\v(.{-}\t){7}/\=substitute(submatch('0'), '\t', ';', 'g')/|%s/\t/,/g

Broken down this is a set of three substitute commands. The first two are cobbled together with a sub-replace-expression:

%s/\v(.{-}\t){7}/\=substitute(submatch('0'), '\t', ';', 'g')/

What this does is find exactly seven occurrances ({7}) of any character followed by a tab, in a non-greedy way. ((.{-}\t)). Then we replace this entire match (submatch(0)) with the result of the substitute expression (\=substitute(...)). The substitute expression is simple by comparison as it just converts all tabs to semicolons.

The last substitute just changes any other tabs on the line to commas.

See :help sub-replace-expression

Upvotes: 2

wheelbarrow
wheelbarrow

Reputation: 335

We solved the issue by just capturing the first 8 groups manually ([^\t]*\t)(...)(...) and then separate them with a semicolon (\1;\2;...;) then replacing the remaining tabs with comma's | 2,$s/\t/,/g

Thanks to everyone trying to help!

Upvotes: 0

Ingo Karkat
Ingo Karkat

Reputation: 172570

My PatternsOnText plugin has (among others) a :SubstituteSelected command that allows to specify the match positions. With this, you can easily replace the first 8 tabs with semicolons, and then use a regular substitute to change the remaining tabs into commas:

:2,$SubstituteSelected/\t/;/g 1-8
:2,$s/\t/,/g

Upvotes: 0

Amadan
Amadan

Reputation: 198314

:2,$s/\t\(.*\t\)\@=/;/g
:2,$s/\t/,
  • Change any tabs where there is a tab later to ;
  • Change any remaining tabs to ,

EDIT: Misunderstood. Here is a fixed version:

:2,$s/\(\(\t.*\)\{7}\)\@<=\t/,/g
:2,$s/\t/;/g
  • Change any tabs where there's seven tabs before it to ,
  • Change any remaining tabs to ;

Upvotes: 0

DJMcMayhem
DJMcMayhem

Reputation: 7679

Here's one way you could do it:

:let @q=":s/\t/;\<cr>"
:2,$norm 7@q
:2,$s/\t/,/g

Explanation:

First, we define a macro 'q' that will replace one tab with a semicolon. Now, on any line we can simply run this macro n times to replace the first n tabs. To automatically do this to every line, we use the norm command:

:2,$norm 7@q

This is essentially the same thing as literally typing 7@q (e.g. "run macro 'q' seven times") on every line in the specified range. From there, we can simply replace every tab with a comma.

:2,$s/\t/,/g

Upvotes: 0

Related Questions