1

Here is a text with lines containing simple words:

AZREG AREDB Z ARGHREHV AZRZEGV RYUJUS

How to replace this line with only the first and the last word separated by a space?

I have ended up with :! awk '{print $1 " " $NF}' but I was wondering if there was a full Vim solution…

Edit: VIM - Vi IMproved 7.4

2 Answers 2

4

Using substitution:

:s/^\(\w\+\).*\s\(\w\+\)$/\1 \2/

Explanation:

  • This is a single line substitution (:s). To apply across the entire file use :%s.
  • The pattern specifies alphanumeric (and underscore) words (\w\+) anchored to the start (^) and the end ($).
  • .* slurps up everything short of a final whitespace character (\s). Without this whitespace .* would slurp up all remaining characters except for a single \w because * is a "greedy" wildcard.
  • By surrounding the start and end word in the pattern with \( and \) we can refer to them in the substitution with \1 and \2. These are called "back references".

Improving things, I prefer 'very magic' mode to make the pattern easier to read. And I'd probably use a stricter version where only lines entirely made up of whitespace separated words are allowed:

:s/\v^(\w+)(\s+\w+)*\s(\w+)$/\1 \3/

Note: with mention of awk I thought at first OP was trying to do this on the command line so I provided the following...

Command line version that updates the file:

vim -e +'%s/^\(\w\+\).*\s\(\w\+\)$/\1 \2/' +'wq' file

Using typical command line semantics (pipe to stdin, print to stdout) to just display the changes:

cat file | vim -e +'%s/^\(\w\+\).*\s\(\w\+\)$/\1 \2/p' +'q!' /dev/stdin
6
  • Nope, applying s/^\(\w\+\).*\(\w\+\)$/\1 \2/g turns the line into AZREG S because the regexp "eats" characters of the last word.
    – greg
    Commented Oct 25, 2017 at 10:26
  • Thanks. I don't know how I missed that as my test of the quick brown fox turned into the x. :) Sometimes you see what you want to see.
    – B Layer
    Commented Oct 25, 2017 at 10:28
  • On the subject, I prefer :%s/\v^\S+\zs.{-}\ze\S+$/ /. Indeed, \w exact definition depends on 'isk', and its contrary is not \s. Commented Oct 25, 2017 at 15:19
  • @LucHermitte Yes but the text in the question doesn't give me the impresson OP would be interested in separating all non-whitespace. Together with \zs/\ze this version might generate more questions than it answers. I'm favoring simplicity/familiarity over elegance here.
    – B Layer
    Commented Oct 26, 2017 at 2:28
  • Regarding groups, today, I found \zs simpler. Regarding \S and \s, what I mean is that \s\w is incorrect and may not match things like (something. Your first patterns with \< are more correct. That's why, without more a precise question, I'd rather show \S: it has less side effects. Commented Oct 26, 2017 at 8:12
4

Here's a series of normal mode keystrokes that will perform the required action on a single line:

wv$bhd

How to apply this edit to every line in the file

To run this on every line in your file you can use the :normal command:

:%norm! wv$bhd

You can also apply this to a particular set of lines with a range (see :help cmdline-ranges) or a global command (see :help :global).

How to deal with indented lines

N.B. The sequence of commands described above will fail if the line is indented. In order to deal with indented lines, you will need first to move to the first non-blank character on the line with the ^ command:

^wv$bhd

Complete explanation of commands used

  • ^: Move to first non-blank character on line,
  • w: Move to beginning of next word on line,
  • v: Enter visual mode,
  • $: Move to the end of the line,
  • b: Move to the beginning of the last word on the line,
  • h: Move one column to the left,
  • d: Delete the visual selection.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.