← back to latest

Replace text inside one column and leave the rest of the line alone

V-BLOCK:'<,'>s/\%V-/./gReplace text inside one column and leave the rest of the line alone

anatomy

:
Enter command-line mode. From visual mode, vim fills in the range '<,'> for you.
'<,'>
The range: every line touched by the last visual selection, from its first line to its last. Ranges are always whole lines, even when the selection was a block.
s
The substitute command. Replaces matches of a pattern on each line in the range.
/\%V
A zero-width atom that matches only at positions inside the last visual area. For a block selection, that means inside its columns, not only its lines.
-
The text to find. Combined with \%V, only hyphens that start inside the block match.
/./
The replacement. Each matching hyphen becomes a period.
g
Global flag. Replace every match on the line, not only the first. Without it, only the first hyphen of each date changes.

In the buffer

You have a build log and you want the dates written with periods instead of hyphens. The build names and statuses also contain hyphens, and those must stay. You have selected the date column with <C-v>2j9l. Before:

2026-10-12  build-01  passed
2026-10-13  build-02  failed
2026-10-14  build-03  re-run
^

After typing :'<,'>s/\%V-/./g and pressing Enter:

2026.10.12  build-01  passed
2026.10.13  build-02  failed
2026.10.14  build-03  re-run
^

Six hyphens change, all inside the selected columns. build-01 and re-run keep theirs. The cursor lands at the start of the last line that had a substitution.

When you would reach for it

You have selected a column in block mode, typed :, run a substitution, and watched it rewrite the whole line, because the '<,'> range only ever means “these lines.” Tables, aligned logs, fixed-width reports and columns of numbers all have this problem: the text you want to change also appears outside the column. \%V is the missing half. The range picks the lines, and \%V tells the pattern which columns count.

Gotchas

  • Leave out \%V and the block is ignored. :'<,'>s/-/./g on the same selection changes all ten hyphens, including build.01 and re.run. The range has no notion of columns; only the pattern does.
  • \%V checks one position, the place where it sits in the pattern. A multi-character match can start inside the block and run past its edge. With only 2026- selected, \%V-1 still matches -1 and eats the 1 outside the block. To pin both ends, put a second \%V before the last character: \%V-\%V1 matches nothing there, which is correct.
  • \%V reads the last visual area, the one gv would reselect, not the current range. That means it keeps working after you leave visual mode and even with :%s, but if you make another selection in between, the pattern follows the new one.
  • Extending the block with $ makes it ragged, reaching to the end of every line however long. \%V honors that, so <C-v>2j$ from the build column changes re-run too.

Variants

:'<,'>s/\%V\d\+/0/g

Any pattern works after \%V. This zeroes out every number that starts inside the block and leaves numbers in other columns alone.

:'<,'>s/\%Vfoo\%Vo/bar/g

The guarded form for longer matches: \%V at the start and before the last character, so the whole fooo must sit inside the selection.

gv

Reselects the area that \%V is using. Run it first when you are unsure which columns the pattern will see.

lineage

Vi had no visual mode, so it had nothing for a pattern to be inside of. Vim added visual mode and the '< and '> marks, and later gave the regex engine the \%V atom so a pattern could read those marks directly. Neovim inherited it unchanged.

-- INSERT --vim-hacks.com029-029-substitute-only-inside-a-block-selection.html
utf-81,1All