For a longer running vim session, the number of buffers increases. However, often $VIMRUNTIME files, system files and netrw buffers can usually be closed with “closed” eyes. These are typically read-only. Currently, I close once in a while vim and reopen the interesting files via CtrlPMRU (configured to ignore many files as those just mentioned). This seems to be a suboptimal workflow.

I have found following question with a good answer on stackoverflow on how to close multiple buffers in a single step:


However, you have to come up with a globbing expression to select the ones you wish to close. In my case I want to close all read-only buffers which cannot be adressed this way.

Does anyone know how to close read-only buffers conveniently?


This command should output the number of the last buffer in the buffer list (see :h bufnr()):

:echo bufnr('$')

This command should output the range of buffer numbers, from the first one to the last one (see :h range()):

:echo range(1, bufnr('$'))

This command should output the 'readonly' option of the buffer 123 (see :h getbufvar()):

:echo getbufvar(123, '&l:ro')

This command should remove from the list of buffer numbers [1,2,3,4,5], the ones whose 'readonly' option is unset (see :h filter()):

:echo filter([1,2,3,4,5], {i,v -> getbufvar(v, '&l:ro') == 1})

This command should output the whole range of buffer numbers, whose 'readonly' option is set:

:echo filter(range(1, bufnr('$')), {i,v -> getbufvar(v, '&l:ro') == 1})

You could join the numbers of the last command, and pass them to :bd (see :h :exe and :h join()):

:exe 'bd '.join(filter(range(1, bufnr('$')), {i,v -> getbufvar(v, '&l:ro') == 1}))

And to make this simpler to execute, you could install a custom :BdReadOnly command (see :h :com):

com! -bar  BdReadOnly  exe 'bd '.join(filter(range(1, bufnr('$')), {i,v -> getbufvar(v, '&l:ro') == 1}))

There may be “holes” in the range. That is, some index may have been used in the past for a buffer, but the latter may have been deleted since then. In this case, the index is not re-used for another buffer. For such non-existing buffer, getbufvar() should then return an empty string:

:echo getbufvar(999999999, '&l:ro') is# ''
    → 1

So, the previous test in the lambda expression should fail:

getbufvar(v, '&l:ro') == 1

And, I think, the index should be correctly filtered.

Also, if the buffers which are deleted are not exactly the ones you want, you may need to tweak the condition getbufvar(v, '&l:ro') == 1 in the lambda expression, and build a more complex one.
In this case, you could be interested in some other functions (see :h buffer-functions).

To name a few:

  • bufexists(123) will tell you whether the buffer 123 exists
  • buflisted(123) will tell you whether the buffer 123 is currently listed
  • bufname(123) will give you the name of the buffer 123, and empty(bufname(123)) will test whether it has one in the first place
  • win_findbuf(123) will tell you in which window(s) the buffer 123 is being displayed, and empty(win_findbuf(123)) will tell you whether it is displayed anywhere in the first place
  • 3
    Note that the arrow lambda style is vim8 only, I believe.
    – D. Ben Knoble
    Oct 9 '18 at 22:06

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.