vim에서 패턴에 매칭되는 영역을 추출하기

vim에서 패턴에 매칭되는 영역을 추출하기
Photo by Nick Harsell / Unsplash

아래 글에서는 notepad++에서 Mark 기능을 사용해서 특정 패턴에 매치되는 부분만을 추출하는 방법을 소개했습니다.

Notepad++ - 특정 패턴의 단어나 문구만 추출하여 정리하기
최근에 텍스트로 된 CSV, JSON 파일과 엑셀을 모두 사용하면서 지난한 작업을 많이 하고 있는데, 다시금 Notepad++의 덕을 많이 보고 있습니다. 단순히 데이터를 포맷팅하거나, 단순한 찾기/바꾸기 외에도 제품바코드 번호나 주문번호 같은 특정 규격의 정보를 큰 파일에서 추출한다거나, 엑셀 파일에서 칼럼을 복사해와서 컴마로 연결하거나 하는 일을 주로 하게 됩니다. 오늘은

그렇다면 vim에서는 이와 같은 작업을 할 수 있는 방법이 있을까요?

:substitute

:s 명령은 버퍼 내에서 특정한 패턴에 매칭하는 부분들을 치환하여 변경하는 명령입니다. 특정 패턴을 다른 고정된 단어나, 혹은 패턴 내 그룹의 배치를 변경하는 식으로 포맷을 바꿀 수 있죠.

" 전체 문서에서 'apple'을 'banana'로 치환
:%s/apple/banana/g

" 11/09/24의 패턴을 2024-09-11로 변경
:%s#\(\d\{2\}\)/\(\d\{2\}\)/\(\d\{2\}\)#20\3-\2-\1/g

치환 명령에서 '바꿀 패턴'에 해당하는 부분은 고정된 문자열이거나, 패턴 내에 그룹이 포함된다면 그룹을 가리키는 패턴이 들어가는 정도입니다.

💡
치환 명령에서 백슬래시가 너무 정신없을 때에는 패턴의 앞에 \v를 써서 very magic 모드를 사용하면 정신건강에 좋습니다.
:%s#\v(\d{2})/(\d{2})/(\d{2})#20\3-\2-\1#g

만약 특정한 패턴에 맞는 단어를 찾아서 그 단어를 대문자로 변환하여 치환하는 것은 어떻게 찾기/바꾸기 기능으로 구현할 수 있을까요? 일반적인 편집기의 찾기/바꾸기 기능은 매치된 단어에 대한 추가적인 연산을 허용하지 않기 때문에, 순수하게 치환 기능만으로는 이를 구현하기 어렵습니다. 보통은 별도의 플러그인이나 아니면 파이썬과 같은 프로그래밍의 영역으로 넘어가는 문제입니다.

하지만 vim은 vimscript라는 스크립트 언어를 내장하고 있고, 이 언어에서 기본적인 문자열 처리를 할 수 있기 때문에, vim에서는 구현할 수 있습니다.

\= 으로 시작하는 치환패턴

"..m"으로 끝나는 단어를 모두 대문자로 만들기

:%s/\v\s(\w+m)\s/\=' ' .. toupper(submatch(1)) .. ' '/g

치환 패턴부분이 \=로 시작하게 되면 vim은 치환 패턴을 vimscript 표현식으로 평가하게 됩니다. 치환해야 하는 문자열은 이 표현식이 리턴하는 문자열 값이 됩니다.

:s 명령의 치환 패턴은 각각의 매치가 발생할 때마다 평가되므로, 이 위치에서 패턴에 매치한 내용들을 어딘가에 모아두었다가 새로운 버퍼에 각 라인별로 붙여넣어주면 되지 않을까요?

이 시나리오를 vim 명령으로 구현해보면 다음과 같습니다.

:let @a = '' | %s/\v\s(\w+m)\s/\=setreg('A', submatch(1), 'l')/gn |\
  new | put! A

해설

setreg(레지스터, 문자열, 옵션)함수는 지정한 레지스터에 문자열을 기록하는 함수입니다. 만약 레지스터 이름이 대문자로 주어지면 write 모드 대신 append 모드가 됩니다. 그리고 옵션의 l은 'line-wise'를 의미합니다. 즉 덧붙이는 각각의 문자열은 개행으로 구분한다는 의미입니다.

submatch() 함수는 좀 특별한 함수로, :substitute 명령의 치환패턴 내에서만 작동하는 함수입니다. 이 함수는 패턴 매칭된 결과에 해당하는 문자열을 반환합니다. 파라미터로는 0부터 시작하는 숫자를 받는데, 눈치 빠른 분들은 알아차리셨겠지만, 0은 매칭된 문자열 전체, 1, 2, .. 부터는 각각의 그룹에 해당하는 내용을 의미합니다.

그리고 :s 함수의 마지막 옵션에서 n 이 붙어있는데, 이 옵션은 몇 개의 라인에서 몇 개의 매치를 찾았는지만 보고하고 실제로는 치환을 실행하지 않습니다. (어차피 setreg() 함수는 전달한 문자열을 그대로 리턴하기 때문에 내용 자체는 변하지 않습니다.)

응용

이 방법을 사용자 정의 명령으로 정의해두고 사용하면 편할 것 같습니다. 아래에는 먼저 vim의 검색 명령(/)으로 검색했던 패턴에 매치되는 모든 문자열을 각 라인별로 새로운 버퍼에 붙여주는 명령입니다.

vimscript9

command -nargs=0 ExtractSearch ExtractSearchPattern()

def ExtractSearchPattern()
  if @/ == ''
    return
  endif
  var temp = @a
  @a = ''
  # :s 에서 검색패턴이 없으면 마지막 검색패턴을 사용
  silent :%s//\=setreg('A', submatch(0), 'al')/gn
  if @a == ''
    return
  endif
  new
  put! a
  normal! gg
  nohls
  @a = temp
enddef

검색 패턴을 같이 입력받는 방법도 있겠지만, 개인적으로는 먼저 검색해서 매치되는 부분이 있는지 확인한 후에 이 명령만 실행해서 추출하는 방법도 좋을 것 같습니다.

Read more

워드프레스에서 고스트로 이전

워드프레스에서 고스트로 이전

이 글을 쓰면서도 믿기 힘든 사실인데, 블로그라는 걸 처음 시작한지가 20년이 되었습니다. 이글루스에서 처음 시작했다가, SK컴즈가 인수한다고 발표함과 동시에 워드프레스로 플랫폼을 옮겼죠. 워드프레스오 옮긴 이후에는 호스팅 환경을 이리 저리 옮기긴 했지만 거의 18년 가까이 워드프레스를 사용해온 것 같습니다. 그 동안 워드프레스는 블로깅 툴에서 명실상부한 범용CMS로 발전했습니다. 사실 웬만한 홈페이지들은 이제

By sooop
띄어쓰기에 대한 생각

띄어쓰기에 대한 생각

업무 메일을 쓸 때 가장 많이 쓰는 말 중에 하나가 메일 말미에 ‘업무에 참고 부탁 드립니다.‘인데요, 어느 날부터 아웃룩에서 이 ‘부탁 드립니다’가 틀렸다고 맞춤법 지적을 하기 시작했습니다. 맞는 말은 ‘부탁드립니다’라고 붙여 쓰는 거라고. 사실 아래아한글 시절부터 이전의 MS워드까지, 워드프로세서들의 한국어 맞춤법 검사 실력은 거의 있으나 마나 한

By sooop

구글 포토에서 아이클라우드로 탈출한 후기

한 때 구글 포토가 백업 용량을 무제한으로 제공해 주겠다고해서, 구글 포토를 사용해서 사진을 백업해왔습니다. 물론 이 이야기의 결말은 저나 이 글을 읽고 있는 여러분이나 모두 알고 있습니다. 사실 AI에게 학습 시킬 이미지 데이터를 모으기 위한 것일 뿐이라거나 하는 이야기는 그 당시에도 있었습니다만, 에이 그래도 구글인데 용량은 넉넉하게 주겠지…하는 순진한

By sooop

Julia의 함수 사용팁

연산자의 함수적 표기 Julia의 연산자는 기본적으로 함수이며, 함수 호출 표기와 같은 방식으로 호출하는 것이 가능합니다. 또한 그 자체로 함수이기 때문에 filter(), map() 과 같이 함수를 인자로 받는 함수에도 연산자를 그대로 적용하는 것이 가능합니다. 특히 + 연산자는 sum() 함수와 같이 여러 인자를 받아 인자들의 합을 구할 수 있습니다. 2 + 3 # = 5 +(2,

By sooop