0

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

3

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
  • Nope, applying s/^\(\w\+\).*\(\w\+\)$/\1 \2/g turns the line into AZREG S because the regexp "eats" characters of the last word. – greg Oct 25 '17 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 Oct 25 '17 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. – Luc Hermitte Oct 25 '17 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 Oct 26 '17 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. – Luc Hermitte Oct 26 '17 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, privacy policy and cookie policy

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