왠만한 편집기들은 파일을 저장하기 전에 각 라인의 뒤에 들어있는 쓸데없는 공백들을 제거해주는 기능을 제공한다. (이러한 trailing space들의 해악에 대해서는 따로 말할 필요가 없을 것 같다.) 물론 vim에서도 이걸 사용할 수 있다. :s
명령을 사용하면 각 줄에서 맨 뒤쪽 공백을 제거할 수 있다.
:%s/\s\+$//
간단히 설명하면 이 명령은 다음과 같이 동작한다.
%
으로 전체 라인에 대해서 반복한다.- 패턴은
/\s\+$/
으로 공백문자가 이어지다가 라인의 끝을 만나는 것이다. - 이 패턴을 지운다.
이걸 :autocmd
에 끼얹으면 손쉽게 파일 저장 이전에 실행될 수 있도록 할 수 있다. *
는 모든 파일이라는 의미이므로, *.py,*.js
처럼 바꾸면 파이썬, 자바스크립트 파일에 대해서만 실행할 수도 있다.
:autocmd BufWritePre * %s/\s\+$//
이렇게하면 파일을 저장하려 할 때마다 저 치환 동작을 수행해서 불필요한 공백을 제거할 수 있다. 극히 간단하게 적용할 수 있는 방법이고, 사실 인터넷을 뒤져봐도 워낙 간단한 방법이라 많이 소개된다. 다음과 같은 변형으로 키맵에 연결하는 경우도 있다. (<C-u>
는 명령모드에서 프롬프트에 입력된 내용을 모두 지우는 키 동작이다.)
:nnoremap <F8> :<C-u>%s/\s\+$//<CR>
좀 더 들여다보기
그런데 이 방법은 엄청 간단한 대신에 몇 가지 부작용을 가지고 있다. 첫째로 가장 근본적인 문제는 %s
명령을 사용한다는 것 때문에 발생한다. 범위에 %
를 주는 경우, 모든 라인을 따라 돌아가면서 명령을 반복하게 되는데, 이 때 커서가 같이 따라 움직이게 된다. 따라서 저장을 마치고 나면 편집하던 위치가 아니라 항상 파일의 맨 끝으로 가게 된다. (물론 이건 <C-o>
로 되돌아가면 되긴 하다)
다른 문제는 :s
명령은 검색 레지스터를 사용한다는 것이다. 따라서 특정 단어를 검색하고, 해당 단어가 하이라이트 된 중에 파일을 저장했다면 찾고 있던 내역이 사라지는 것이다. 다만 autocmd
는 항상 실행 전후에 검색 레지스터를 백업했다가 복구한다. 이 문제는 키맵으로 설정하는 경우에 다르게 처리해야 한다.
셋째로 이미 끝자리 공백이 모두 제거된 파일에 대해서는 치환이 실패할 것이므로 매번 E486: Pattern not found: \s\+$
이라는 에러 메시지가 표시될 것이다.
이를 해결할 수 있는 실마리들은 다음과 같다.
- 치환 후 원래위치를 복원하기 : 현재 커서 위치의 라인과, 스크롤 상태 등을 따로 기억했다가 복원하는 여러 팁들이 있는데, 가장 간단한 방법은
winsaveview()
함수를 통해서 현재 화면의 상태를 얻고 나중에winrestview()
함수를 통해서 복원하는 것이다. - 역시 검색 레지스터는 스크립트에서 쓰기/읽기가 가능하므로 백업/복원이 가능하다.
:s
명령의 플래그중에는 치환실패 메시지를 출력하지 않게하는 e 옵션이 있다.
이를 토대로 다음과 같이 코드를 수정할 수 있다. 다만 map 명령에서 키 시퀀스 부분을 여러 줄로 분할하여 입력할 수는 없으므로 엄청 긴 키맵을 입력해야하는 부담이 있다.
nnoremap <F8> :<C-u>let _x = @/<Bar>let _w = winsaveview()<Bar>%s/\s\+$//e<Bar>let @/ = _x<Bar>call winrestview(_w)<CR>
또 키맵이나 autocmd
의 경우 변수를 추가로 노출하기 때문에 깔끔한 방법이 아니다. 따라서 다음과 같이 함수로 정의해서 저장 할 때, 키를 눌렀을 때 작동할 수 있도록 하자.
function! s:remove_trailing_spaces() abort
let x = @/
let w = winsaveview()
silent! %s/\s\+$//e "silent 명령으로 치환 결과를 표시 안함
let @/ = x
call winrestview(w)
endfunction
이 함수를 호출하는 사용자 정의 Ex 명령과 키맵을 작성한다. 그리고 적절한 키와 자동명령에서 호출되도록 해주면 된다.
" 명령으로 정의
command! -nargs=0 RemoveTrailingSpaces
\ call <SID>remove_trailing_spaces()
" 저장시마다 발동
augroup TS
au!
au BufWritePre * RemoveTrailingSpaces
augroup END
" 키맵으로 정의해서 원할때마다 실행하려면
noremap <script><silent> <Plug>(remove_trailing_spaces)
\ :<C-u>RemoveTrailingSpaces()<CR>
nmap <F8> <Plug>(remove_trailing_spaces)
이상의 코드를 vim파일로 작성해놓고 :source
명령으로 읽어들이면 자동으로 혹은 수동으로 실행할 수 있게 된다.
보너스 – 파일 끝의 빈줄 제거하기
줄 끝의 공백만큼이나 많이 생기는 것이 파일 끝의 빈 줄이다. 이것도 간단히 처리할 수 있다. 파일끝 공백은 개행문자로부터 시작하여 공백-개행-공백-개행-…-공백이 계속해서 이어진다. 결국 (개행공백*)
의 반복이다. 그리고 그 끝은 파일의 끝이다. vim 검색 패턴에서 \%$
는 파일의 끝을 의미하므로 다음과 같은 명령으로 파일 끝 빈 줄을 제거할 수 있다.
:s/\(\n\s\+\)\+\%$//e
참고로, 파일 끝 빈줄은 파일 내에서 한 번만 나올 것이기 때문에 %
를 붙이지 않는다. (붙여도 상관은 없다만…) slient s/\(\n\s\+\)\+\%$//e
를 위에서 작성한 함수에 추가해주면 깔끔하게 정리 성공.