如何在Vim中删除文件末尾的空行?

40
有时我会在编辑文件时意外地留下空白行。
如何在Vim保存时删除它们?

更新

谢谢大家,所有的解决方案似乎都可以工作。
不幸的是,它们都会重置当前光标位置,所以我编写了以下函数。
function TrimEndLines()
    let save_cursor = getpos(".")
    silent! %s#\($\n\s*\)\+\%$##
    call setpos('.', save_cursor)
endfunction

autocmd BufWritePre *.py call TrimEndLines()

5
如果保留光标位置对您很重要,您可以在问题中指定这个要求。 - ib.
2
抱歉,我没有想到它会重置游标位置。 - gennad
6个回答

43
这个替换命令应该可以解决问题:
:%s#\($\n\s*\)\+\%$##
请注意,这会删除所有只包含空白字符的尾随行。为了只删除真正的“空”行,请从上述命令中删除\s*
编辑:
解释:
- \( ..... 匹配组的开始 - $\n ... 匹配一个新行(后面跟着回车符的结束行字符)。 - \s* ... 允许此新行上的任意数量的空格。 - \) ..... 结束匹配组。 - \+ ..... 允许此组出现任意次数(一次或多次)。 - \%$ ... 匹配文件的结尾
因此,正则表达式匹配任意数量的仅包含空格的相邻行,仅以文件结尾作为终止条件。然后替换命令用空字符串替换匹配项。

3
@Merlyn Morgan-Graham: #是模式定界符。在使用s命令时,你可以选择它,而/不是必须的。 翻译:@Merlyn Morgan-Graham: #是模式定界符。当使用s命令时,你可以选择它作为定界符,而/并非必需。 - Benoit
5
另一个模式将是 /\_s*\%$ - Benoit
3
请注意该模式,因为它会导致删除最后一个非空行的尾随空格,这可能是不希望发生的。 - ib.
如果有人想在末尾只有一行空行时删除尾随的空行:%s#\([^\s] \)\($ \ n \ s * \)\ {1 \} \%$#\ 1#(参考:https://github.com/drybjed/dotfiles/pull/2) - ypid
当在自动命令中使用此函数时,它有两个错误:1)当没有匹配项时,它会打印一个错误;2)当文件为空时,它会失败。我已经调整了替换命令,像这样:%s/\($\n\)\+\%$//e;另外,我已经将其包装在我的自动命令中,使用 if line('$') > 1 ... endif - rumpelsepp
显示剩余4条评论

16

1. 可以基于:vglobal命令(或者同样的,基于带有!修饰符的:global)来实现优雅的解决方案:

:v/\_s*\S/d
该命令在没有非空格字符的每一行以及其后面的文本到缓冲区末尾执行:delete操作(请参见:help /\s:help /\S:help /\_以了解模式)。因此,该命令删除了结束处的空白行。
要严格删除行(而不是只包含空格的空行),请按照以下方式更改:vglobal命令中的模式。
:v/\n*./d

2. 对于包含大量连续空格字符的巨大稀疏文件(从约数百KB的空格开始),上述命令可能具有无法接受的性能。如果是这种情况,则可以使用相同的优雅思想将那些:vglobal命令转换为速度更快(但可能不太优雅)的基于模式定义的范围:delete命令。

对于空行:

:0;/^\%(\_s*\S\)\@!/,$d

对于空行:

:0;/^\%(\n*.\)\@!/,$d

这两个命令的本质相同,即删除属于指定范围的行,这些范围根据以下三个步骤定义:

  1. 在解释范围的其余部分之前将光标移动到缓冲区的第一行(0;-参见:help :;)。 01行号之间的区别在于,当有搜索模式用于定义范围的结束行时,前者允许在第一行进行匹配。

  2. 搜索某一行,在该行中描述非拖尾空白行的模式(\_s*\S\n*.)不匹配(否定是由于\@!原子--参见:help /\@!)。将范围的起始行设置为该行。

  3. 将范围的结束行设置为缓冲区的最后一行(,$-参见:help :$)。

3. 要在保存时运行上述任何命令,请使用autocommand触发它,并在BufWrite事件(或其同义词BufWritePre)上触发。


3
这不仅更加优雅,而且更加实用。有时候人们会混淆精通某个领域的价值,这并不是通过过度设计自己知识的有限子集来实现,而是要从所使用工具提供的功能中学习新的可能性。 - sidyll
我的初始想法是使用一些带有 :d 的范围,比如 $?\s*.?+d,但我发现这个解决方案要好得多。不幸的是,我必须承认,多行正则表达式以及 :g:v 并不经常发生在我身上。谢谢你分享。 - Peter Rincker
但我怀疑,如果使用大量的空格,这个命令可能会变得不够高效。 - Benoit
1
@Benoit:你的怀疑是正确的:从几百千字节的连续空格开始,:vglobal会变得更慢。然而,可以使用用于检测尾随空白或空行的相同模式来查找从该位置开始到文件末尾要删除的第一行。 (请参见更新的答案。)顺便说一句,在处理巨大的几乎全空格文件时,直接替换解决方案通常无法帮助(它会出现“E363”错误),因为它需要大量内存进行模式匹配(通常超过“maxmempattern”选项的值)。 - ib.
好的回答!你能解释一下最后两个命令吗? - mMontu

9
您可以将此内容放入您的vimrc文件中。
au BufWritePre *.txt $put _ | $;?\(^\s*$\)\@!?+1,$d

(使用任何globbing模式替换*.txt)

详细信息:

  • BufWritePre是将缓冲区写入文件之前的事件。
  • $put _在文件末尾添加一个空行(来自始终为空的寄存器)
  • |链接Ex命令
  • $;?\(^\s*$\)\@!?跳转到文件末尾($),然后(;)向后查找(?…?)第一行不完全为空的行(\(^\s*$\)\@!),还可以参见:help /\@!了解vim搜索中的否定断言。
  • ×××+1,$形成从行号为×××+1直到最后一行的范围
  • d删除行范围。

1
+1;我喜欢使用“$put _”来确保始终有一个空行。 - Peter Rincker
这是一个原创且复杂的命令!它有一个流程。当缓冲区的所有行都不为空白行(例如,想象一下几行只包含单个空格字符),此命令不会删除任何该行!顺便说一句,类似的思路可以用于构建基于“:global”命令的更干净的Ex命令。 - ib.
@ib.: 一个只包含空格的文件在保存之前会被用户注意到(很可能是Whitespace语言的程序!) - Benoit
@Benoit:这可能会被注意到,也可能不会。自动化的整个目的是摆脱需要观察需要运行命令的特定情况。然而,如果文件中没有非空白行,则建议的命令不会删除文件末尾的空行。因此,该命令并不能完全解决问题(问题的措辞没有针对这种情况做出任何例外)。 - ib.

4
受@Prince Goulash解决方案启发,将以下内容添加到您的~/.vimrc中,以便在保存Ruby和Python文件时删除尾随空行:
autocmd FileType ruby,python autocmd BufWritePre <buffer> :%s/\($\n\s*\)\+\%$//e

2

我发现之前使用替换函数在处理大型文件时会出现问题,并且会污染我的寄存器。下面是我想出来的一个函数,对于我来说性能更好,而且避免了寄存器的污染:

" Strip trailing empty newlines
function TrimTrailingLines()
  let lastLine = line('$')
  let lastNonblankLine = prevnonblank(lastLine)
  if lastLine > 0 && lastNonblankLine != lastLine
    silent! execute lastNonblankLine + 1 . ',$delete _'
  endif
endfunction

autocmd BufWritePre <buffer> call TrimTrailingLines()

2

我有一个名为Preserve的单独函数,可以从其他函数中调用, 使用Preserve轻松完成其他任务:

" remove consecutive blank lines
" see Preserve function definition
" another way to remove blank lines :g/^$/,/./-j
" Reference: https://dev59.com/NWs05IYBdhLWcg3wB9ef#7496112
if !exists('*DelBlankLines')
    fun! DelBlankLines() range
        if !&binary && &filetype != 'diff'
            call Preserve(':%s/\s\+$//e')
            call Preserve(':%s/^\n\{2,}/\r/ge')
            call Preserve(':%s/\v($\n\s*)+%$/\r/e')
        endif
    endfun
endif

在我的情况下,我会在文件末尾保留至少一行空白,但不超过一行。

" Utility function that save last search and cursor position
" http://technotales.wordpress.com/2010/03/31/preserve-a-vim-function-that-keeps-your-state/
" video from vimcasts.org: http://vimcasts.org/episodes/tidying-whitespace
" using 'execute' command doesn't overwrite the last search pattern, so I
" don't need to store and restore it.
" preserve function
if !exists('*Preserve')
    function! Preserve(command)
        try
            let l:win_view = winsaveview()
            "silent! keepjumps keeppatterns execute a:command
            silent! execute 'keeppatterns keepjumps ' . a:command
        finally
            call winrestview(l:win_view)
        endtry
    endfunction
endif

以下是我为什么有一个单独的“保存”功能的原因:

command! -nargs=0 Reindent :call Preserve('exec "normal! gg=G"')

" join lines keeping cursor position
nnoremap J :call Preserve(':join')<CR>
nnoremap <Leader>J :call Preserve(':join!')<CR>

" Reloads vimrc after saving but keep cursor position
if !exists('*ReloadVimrcFunction')
    function! ReloadVimrcFunction()
        call Preserve(':source $MYVIMRC')
        " hi Normal guibg=NONE ctermbg=NONE
        windo redraw
        echom "Reloaded init.vim"
    endfunction
endif
noremap <silent> <Leader>v :drop $MYVIMRC<cr>
command! -nargs=0 ReloadVimrc :call ReloadVimrcFunction()

" Strip trailing whitespaces
command! Cls :call Preserve(':%s/\v\s+$//e')

如果您想创建自动命令,必须创建一个组来避免过度加载自动命令:

augroup removetrailingspaces
    au!
    au! BufwritePre *.md,*.py,*.sh,*.zsh,*.txt :call Preserve(':%s/\v\s+$//e')
augroup END

这个是用来改变文件头部的(如果你有任何建议),请随意互动:

" trying avoid searching history in this function
if !exists('*ChangeHeader')
    fun! ChangeHeader() abort
        if line('$')>=7
            call Preserve(':1,7s/\v(Last (Change|Modified)|date):\s+\zs.*/\=strftime("%b %d, %Y - %H:%M")/ei')
        endif
    endfun
endif

" dos2unix ^M
if !exists('*Dos2unixFunction')
    fun! Dos2unixFunction() abort
        "call Preserve('%s/ $//ge')
        call Preserve(":%s/\x0D$//e")
        set ff=unix
        set bomb
        set encoding=utf-8
        set fileencoding=utf-8
    endfun
endif
com! Dos2Unix :call Dos2unixFunction()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接