8

I have a list of files:

./a.temp.txt     ./a.temp.txt
./a/b.temp.txt   ./a/b.temp.txt
./a/b/c.temp.txt ./a/b/c.temp.txt

And I want to remove the temp. on each line, but only the second occurence, thus, the file should look like:

./a.temp.txt     ./a.txt
./a/b.temp.txt   ./a/b.txt
./a/b/c.temp.txt ./a/b/c.txt

How should I do this?

  • Is it possible that there will be a third or fourth occurrence on any line? Do these have to remain intact? – DJMcMayhem Jul 7 '16 at 16:15
  • My case was to match old filename to new filename, so only 2 occurrences will be on each line. And only the second one should change. – nobe4 Jul 7 '16 at 16:16

10 Answers 10

7

In general you can match the Nth occurrence of something using \zs and \{N}. There's an example given at :help \zs.

In your case the command would be:

:%s/\(.\{-}\zstemp\.\)\{2}//
  • with the verymagic flag you can reduce the regex: %s/\v(.{-}\zstemp.){2}// – nobe4 Jul 8 '16 at 6:40
7

You can also do this with a lookbehind.

:%s/\(temp\..*\)\@<=temp\.//

If you want to remove ALL occurrences after the first, you can append the g flag at the end.

\(\)\@<= Will search for any pattern between \( and \) but will not add the found text to the match.

See :h \@<= for more info.

6

This is much easier done with sed:

sed 's/\.temp\././2'

With Vim you need non-greedy matching, and it isn't easy to extend the method to replacing the 3rd, 4th, etc. occurrence of temp. But it can be done if you insist:

:%s/\.temp\..\{-}\.\zstemp\.//
  • Thanks for giving the sed solution, do you think it's possible to apply this on each line ? – nobe4 Jul 7 '16 at 11:28
  • @nobe4 Not sure what you mean by that. sed applies scripts to each line of input in turn. – Sato Katsura Jul 7 '16 at 11:30
  • yes, but you can pass lines into external program and get back the result. – nobe4 Jul 7 '16 at 11:32
  • 2
    You mean from Vim? Sure, you can pipe lines to sed just as you can pipe them to any other program. F.i.: :%!sed 's/\.temp\././2'. sed in UNIX-friendly, it reads input from stdin and writes results to stdout. – Sato Katsura Jul 7 '16 at 11:35
6

You can use a macro as @Meshpi already suggested. But, this is another way you can accomplish the same thing.

02/temp^Mdwx+

0 - Go to the starting of a line

2/temp - Search for temp and go to the second occurance

^M - This is just pressing the Enter key

dw - delete the word (temp)

x - delete the '.'

+ - Go to the beginning of the next line

And, then you can just repeat it till the end. And, there will be a lot more ways to do the same.

5

This solution is similar to TessellatingHeckler's but is more easily adapted to whatever pattern has to be deleted.

:g/temp\./normal 2ngnd

Here's how it works:

  1. :g/temp\./ for every line matching "temp."
  2. normal execute the following in normal mode
    1. 2n find the second ocurrence of the pattern
    2. gn select it
    3. d and delete it.

This will work for whatever pattern is given to :g. normal may be abbreviated to norm. gnd is equivalent to dgn. Edit: In fact 2ngnd is equivalent to d2gn.

See the Vim Tips Wiki for more examples of the global command.

4

In Vim, you can define the column before/at/after which you want your matching to happen with :help \%c:

:%s/\%>17c\.temp/g

This will get rid of every .temp found after column 17 of every line in the current buffer.


Note 1: the /g is not necessary with your sample.

Note 2: I use that handy feature very often with qmv. Recommended!

  • Neat, I was thinking of making a plugin, but this is nice! – nobe4 Jul 7 '16 at 12:35
  • Vim already does so many things… – romainl Jul 7 '16 at 12:37
  • Sure, but I was not aware of this tool. And a little bit of scripting is always fun! – nobe4 Jul 7 '16 at 12:38
  • 6
    @nobe4 If you care about this kind of things, see also vidir and vipe from the moreutils package. – Sato Katsura Jul 7 '16 at 13:17
4

This is actually pretty simple. To match the second temp, allow anything followed by "temp" followed by anything, then look for temp again.

%s/.*temp.*\zstemp\.

The \zs means "Start the selection here" so that the first part of the match (everything before the second temp) isn't removed. It's actually an extremely useful feature that I just learned about! Without it, the regex would have to look like this:

:%s/\(.*temp.*\)temp\./\1
  • If there are more than two temp on a line your recipe deletes the last one, rather than the second. Blame the greed. :) – lcd047 Jul 7 '16 at 20:13
  • 2
    Yes, this is greedy, and so it deletes the last one. However, OP said My case was to match old filename to new filename, so only 2 occurrences will be on each line. And only the second one should change. – DJMcMayhem Jul 7 '16 at 20:15
2

Instead of regex, I'd go with a command which does:

  • Jump to end of line
  • Search backwards for temp
  • Delete 5 characters

and run it on every line:

:g//normal $?temp^[5x

NB. ^[ is special and represents pressing the Escape key; you type it with: Ctrl-q <Esc>


But a regex option, which I don't think has been suggested yet, is to match temp. followed by anything except a ., anchored to the end of the line.

:%s/temp\.\([^.]\+\)$/\1/

Which will match temp.txt at the end of a line and replace it with just the txt bit.

1

I'd write a macro with the cursor starting at the top left. qq4f.dfpq

Then I'd select the rest of the lines using V and run the macro on those lines with :'<,'>norm!@q

This will only work if the text format stays the same in relation to the number of dots before the second temp.

  • 5
    To make your macro more robust, you should use a search and n instead of 4f. because if a filename has more dots, your macro could fail or delete stuff which shouldn't be deleted. – statox Jul 7 '16 at 11:28
1

There is nothing preventing you from using two regexes instead of just one, i.e. change the first occurrence and then swap the columns:

:%s/\.temp//|%s/^\(\S\+\)\(\s\+\)\(\S\+\)/\3\2\1/

Might not be extra clever but I find it very readable.

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.