10

I have a file full of text (say Markdown or LaTeX). I would like to count the number of words in a part of this file.

I know I can do :! wc -w % to run wc -w on the current buffer. And I know that I can yank the section of interest into a named register. I'm guessing there's a way to send a named register to the operating system for use in a command or pipe, but I haven't been able to find one. Or is there a better way to count the words in a register?

My use case is that I do a lot of non-programming writing (notes, thesis, etc) in vim, and I would like to count how many words I've added to a given section of the file in the middle of an editing session.

16

You can use gCTRL+g, which will give you:

Col 1 of 118-121; Line 1 of 5; Word 1 of 142; Byte 1 of 678

You can also use this from visual mode, if you want to get the word count for just the selection, which is especially useful combined with text objects such as ip. (e.g. you can use vipg<C-g> to get the word count of the current paragraph).

See: :help word-count and :help text-objects.


The above option is probably better, but you can also use wc utility to count the number of words in a section. Aside from the :! wc -w % form you use, you can also use:%!wc -w. This will filter a motion to a shell tool (in this case %, the entire buffer), but you can also use other ranges (such as :1,5!wc -w for the first 5 lines, !,+5!wc -w for the current and next 5 lines, etc.). You can also select text in visual mode, and type :!wc -w to filter your selection.

Note that this will replace the motion with the output of the shell tool, but you can u to undo this.

See :help :range!, :help range, and this answer where I give more examples of ranges.

  • I had found something like this while searching, but missed that the first g is part of the counting command, not a location specifier. This solution makes sense now. I apparently should also read up on Visual mode; I don't use it often enough. – Colin McFaul Jun 28 '15 at 0:31
  • 1
    I had no idea that you could use g<C-g> that way. Awesome! – EvergreenTree Jun 28 '15 at 0:48
3

There are two ways this can be accomplished, the pure vimscript way and the wc way.

The pure vim way

You can use the search and replace command to do this. For example:

:%s/\<\w\{-}\>//gn

What this does is instead of replacing a given pattern with something, it just counts the occurrences of the pattern. This is because of the n flag. To count the words in a specific section (in this case lines 5 through 15), you could do something like this:

:5,15s/\<\w\{-}\>//gn

This removes the need to yank the contents of a selection into a register. To see more possibilities for what can be put in place of 5-15, read the help topic for cmdline-ranges. If you want to do this often, it is probably good to create a mapping (or command) for it. Also, if you have hlsearch enabled, you might want to run :nohlsearch afterwards to clear the highlighting.

The wc way

The same thing can be accomplished with wc. In the same way you can use cmdline-ranges to select the area with the :s command, you can use them with external commands. For example:

:5,15!wc -w

This runs lines 5 through 15 through the wc command. The downside to this is that it replaces that range of lines with the output of the command. You can undo this change by pressing u. Also note that the vimscript solution may not work with different languages, because \w does not match what would normally be word characters in other languages. wc may do better at this than \w. Also, here is a fancy command to make it faster to do this:

command -range=% -addr=lines WordCount execute '<count>!wc -w' | .y a | undo | echo @a

Note that this clobbers the a register.

Note

It appears that this can also be accomplished in visual mode with the g<C-g> key combination. See Carpetsmoker's answer for an explanation of this.

  • These need a g along with the n in order to make them global (otherwise, they only match one word per line). The second one also needs an s at the beginning. – Colin McFaul Jun 28 '15 at 0:29
  • 1
    Fixed, sorry about that. – EvergreenTree Jun 28 '15 at 0:32
  • 1
    Using \w sounds like a nice idea at first, but after testing it I found a number of problems. The biggest is that it won't match non-ascii characters, so a word like über is simply skipped (there was a question about this yesterday). Also, a word like e-mail is counted as 2 words, since - is not in \w (using a - is somewhat uncommon in English, but very common in Dutch for example). There may be other characters which are ignored in this fashion, which brings us to my last point: conventions as to what is considered a "word" may differ ... – Martin Tournoij Jun 28 '15 at 0:43
  • ... in various languages, and "proper" tools like wc may pick up on the locale (I don't know if GNU wc actually deals with this by the way, GNU tools are not well known for their excellent unicode support). – Martin Tournoij Jun 28 '15 at 0:43
  • That is interesting. I might add that as a plus to the wc solution. – EvergreenTree Jun 28 '15 at 0:44
1

For words use:

:.,+4 s/\i\+/&/gn

. denotes the current line.

Also I put the following in my .vimrc file:

:cabbrev zzcc   s/./&/gn

:cabbrev zzcw   s/\i\+/&/g

I can type:

:.,+6 zzcw

and the zzcw will expand to s/\i\+/&/g

The zzcw is just an odd name that won't match anything (for me).

A side effect is the entire file is selected and highlighted.

I wanted to be able to type multi-line tweets in a file, make sure there weren't too many characters, and paste the tweet in twitter.

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.