콘텐츠로 건너뛰기
Home » 열려진 모든 파일에서 문자열 찾기 – Vim

열려진 모든 파일에서 문자열 찾기 – Vim

:bufdo 명령

:bufdo는 버퍼 리스트의 모든 버퍼에 대해서 동일한 명령을 반복 수행하는 기능으로, 찾기/바꾸기 외에도 어떤 명령이든 여러 파일에 대해서 한꺼번에 적용하는 기능을 제공한다. 참고로 :silent 처럼 문자열이 아닌 일반 명령 시퀀스가 뒤에 오게 된다. 예를 들어 vim에서 열어놓은 모든 버퍼에서 ‘sample’ 이라는 단어를 ‘fixed’라고 변경하려면, 모든 버퍼를 일일이 옮겨다닐 필요 없이 다음과 같이 :bufdo %s/sample/fixed/g를 실행하면 된다.

이와 비슷하게 모든 창에 대해서 동일한 명령을 반복하거나, 탭 단위로 반복할 수 있다. :windo 명령은 모든 창에 대해서 적용되며, (따라서 창에서 보이지 않는 버퍼에 대해서는 실행되지 않는다.) :tabdo 은 모든 탭에서 실행된다.

결과를 어떻게 표시할까

따라서 :bufdo 명령을 사용하면 모든 버퍼에서 특정 패턴을 검색할 수 있을 것 같다. 그런데 찾는 범위는 전체 버퍼가 되지만, 검색한 결과는 어느 한 곳에 모아서 보여줘야, 한 눈에 어느 곳에 찾는 내용이 있는지를 볼 수 있을 것이다. 보통 현재 버퍼에서는 /pattern 을 실행해서 문자열/패턴을 검색하는데, 이와 별개로 :vimgrep 이라는 명령이 있다.

:vimgrep /{pattern}/ {files...}

참고로 외부 명령을 사용하여 검색하는 :grep 명령도 있다. &grepprg, &grepformat 옵션을 잘 설정하면 좀 더 나은 성능을 보여주기도 한다.

이 명령은 패턴에 대해서 검색을 수행하고, 검색된 위치로 점프하는 대신, 검색한 결과를 quick-fix 창에 기록한다. 이 결과를 보려면 :copen이나 :cwindow 명령을 사용해서 창을 열면 된다. 만약 현재 파일에서 검색하려면 파일 명을 % 으로 준다. 다음 명령은 현재 버퍼에서 “await”라는 단어를 표시하고, 모든 라인을 볼 수 있게 quickfix 창을 열어준다. (뒤에 10은 qf 창의 높이이다.)

:vimgrep /await/ % | copen 10

quick-fix 창에는 찾는 위치를 선택하고 Enter를 누르면 해당 위치로 점프하게 된다. :cclose 를 실행하면 현재 창에서 바로 qf 창을 닫을 수 있다.

여러 버퍼의 검색 결과를 모으기

그럼 :bufdo vimgrep을 쓰면 해결되느냐? 아직 갈 길이 멀다. :vimgrep 명령은 매 실행시마다 이전의 결과 리스트를 초기화하기 때문에 하나의 버퍼에서 사용하는 것은 문제가 되지 않는다. 그러나 :bufdo를 써서 반복 수행하면 맨 마지막 버퍼 이전의 검색결과가 제거되기 때문에 원하는 결과를 얻을 수 없다.

:vimgrep에 대한 변형이 있는데, 바로 :vimgrepadd이다. 뒤에 “add”가 붙은데서 짐작하겠지만, 이 명은 이전의 결과를 지우지 않고 결과를 이전 목록 아래에 계속 추가한다. 주의할 점은 각 버퍼별로 돌아가면서 검색할 때에는 결과를 붙여나가야 하지만 최초 버퍼에 검색하기 이전에는 quick-fix 리스트를 비워야 한다는 점이다. quick-fix 리스트를 제어하는 함수로 setqflist()가 제공된다. 다음 함수는 인자로 받은 단어를 qf 리스트로 만들어서 보여주는 기능을 수행한다. 이를 사용자 정의 명령으로 사용하려면 인자로 <q-args>를 넘겨준다. 이는 :FFindAll print 라고 입력한 것을 find_in_all_buffer("print")로 전달하겠다는 의미이다.

function! s:find_in_all_buffer(item)
	call setqflist([])
	silent exec "bufdo vimgrepadd /" . a:item . "/ %"
	copen
endfunction

if !exists(":FFindAll")
	command -nargs=1 FFindAll 
		\silent! call <SID>find_in_all_buffer(<q-args>)
endif

이 기능을 자주 쓰게 된다면 따로 사용자 정의 명령으로 지정해두면 도움이 된다. 그런데 명령줄 입력시에 따옴표를 치지 않고 입력한 값들은 표현식으로 인식되기 때문에, 에러 없이 함수의 인자로 넘겨주기 위해서는 <args> 대신 q-를 붙인 <q-args>를 사용하도록 하자.

:현재창 검색을 위치 창으로 보기

quick-fix 창은 본래 컴파일-에러확인-편집 사이클을 빠르게 하기 위해 사용되는 도구로, 여러 파일의 에러/경고가 발생한 위치를 저장하는 용도로 쓰이며, 여러 언어에서 소스 코드가 병합되는 처리를 거치는 경우가 많아서 여러 버퍼에 걸쳐서 작동하도록 되어 있다. 위치창(location window)은 윈도에 종속된 window-local의 quick-fix 창과 같은 버전으로, 창마다 별도의 위치를 보여주는데 사용할 수 있다. vim 명령중에 vimgrep 과 같이 quick-fix 창을 쓰는 명령들은 앞에 l(소문자 L)이 붙어서 QF창 대신 위치 창을 쓰도록 하는 명령들이 있다. :lmake, :lgrep, :lhelpgrep, :lvimgrep등이 그러한 명령들이다. 위치 창은 각 창에 종속되며, 각각의 창은 별개의 위치창을 가질 수 있다.

따라서 현재 버퍼에서 특정 키워드를 모두 찾는 명령은 :lvimgrep으로 대체할 수 있으며, 이 경우 :copen 명령은 :lopen 명령으로 대체하면 된다.

:lvimgrep /keyword/ % | lopen

이 동작을 자주 사용할 것 같다면 다음과 같이 함수와 키맵으로 정의해놓을 수 있다. 참고로 setlocalist()의 첫 인자는 버퍼 번호인데, 0을 넘겨주면 현재 버퍼의 번호로 대체된다.

function! s:find_cword_in_buffer()
	" set current buffer's loc list to empty
	call setloclist(0, [])
	silent! exec 'lvimgrep /' . expand('<cword>') . '/ %'
	lopen
endfunction

nnoremap <silent> <leader>*  :call <SID>find_cword_in_buffer()<CR>

보너스 – 버퍼와 창을 구분하자

버퍼는 vim에서 열어놓은 파일, 정확히는 열어놓은 파일이 올려져있는 편집공간을 의미한다. 창은 버퍼의 내용을 화면에 표시하는 영역이다. :split 명령으로 현재 버퍼의 내용을 2개 창에서 표현할 수 있으며, 창은 하나이지만 여러 개의 버퍼가 열려져 있을 수 있다. (:bn, :bp 명령으로 이전/이후 버퍼로 이동할 수 있다.) 그리고 :newtab 명령을 통해서 새로운 탭을 만들 수 있는데, 하나의 탭은 1개 이상의 윈도로 구성될 수 있다. 그래서 정리하자면 1) vim은 1개 이상의 탭을 만들 수 있다. 2) 각 탭은 1개 이상의 윈도로 구성되며, 나누기 명령을 통해서 영역을 분리할 수 있다. 3) 각 윈도는 어떤 버퍼를 표시하는 영역이라고 이해하면 된다. 단순히 창을 닫는 다고해서 열었던 버퍼가 완전히 닫히는 것은 아니다.

모든 버퍼는 버퍼의 번호와 이름을 가지고 있다. 또 모든 창은 창번호와 그 창에서 현재 표시하고 있는 버퍼의 번호를 가지고 있다.

  • winbufnr(wn) : 주어진 창에서 표시중인 버퍼의 번호 win -> bufnr로 이해하면 된다.
  • bufwinnr(bn) : 반대로 주어진 버퍼가 표시되는 창 번호를 찾는다. (buf -> winnr)
  • bufname(bn) : 주어진 버퍼 번호의 이름