15 Vim 심화- 찾아 바꾸기 (substitute)

이번 시간에는 Vim 에서 어떻게 '찾아 바꾸기'를 하는지 알아보도록 하자. 'Vim 에서 찾기' 기능은 앞선 '기본조작'편에서 이미 알아보았다. '찾기'는 [The Vim Way] 09 Vim 기본조작 - Vim 문자열 찾기 편에서 알아볼 수 있다.

 

Vim 찾아 바꾸기

요즘 IDE 또는 텍스트에디터에서는 '찾아 바꾸기'는 매우 기본적으로 탑재되어있는 기능 중에 하나이다. 특히 jetbrains 계열의 IDE 에서는 정규표현식, 대소문자 구분 여부, 단일 단어 여부에 따라 찾기 및 찾아바꾸기 기능을 지원한다. 즉, vim 에서 제공하는 찾아바꾸기를 굳이 이용할 이유가 없는 것도 사실이지만, original vim 을 이용해야하는 상황이거나, 범위를 지정하여 찾아바꾸기를 진행해야 하는 경우 등의 상황이라면 이번 포스팅에서 다룬 내용이 유용할 수 있을 것이다. 이 포스팅은 '과도하게(?) 자세히' 본 기능에 대해서 소개한다. 바쁜 당신을 위해 바로 아래 '빠르게 살펴보기'를 준비했다.

Vim 찾아바꾸기 Quick View

Normal 모드에서 다음을 입력하면 문서 전체에 대하여 나타나는 모든 'something' 을 'sth' 로 변경한다.

(대소문자 관계 없이 - case insenstive - 하게 변경하고자 한다면, g 뒤에 i 를 덧붙여준다.)

:%s/something/sth/g

아래는 이 명령의 동작 원리를 상세하게 풀어 설명한다.

Vim 찾아 바꾸기 기본 (현재 라인)

vim 에서 찾아 바꾸기는 '커맨드모드'에서 이루어진다. 커맨드모드에서 다음 명령어를 입력하여 현재 커서가 위치하는 라인에 대하여 '찾아 바꾸기'를 수행할 수 있다.

  1. : 로 커맨드모드 진입
  2. s/{찾을 대상 regex}/{바꿀 문자열} 입력

:s 는 커맨드모드에서 '찾아바꾸기'를 의미하는 명령이다. (substitute 의 첫 글자)

첫 번째 슬래시(/) 이후에는 '찾을 문자열(정규표현식)'을 입력한다. (보통 pattern 이라고 표기한다.)

두 번째 슬래시(/) 이후에는 '바꿀 문자열'을 입력한다.

 

가령 다음과 같이 입력하면 'show' 라는 문자열을 'give' 라는 문자열로 변경하는 명령어를 의미하게 된다. 단, 커서가 위치한 라인만 해당 사항이 적용된다는 것과, '찾을 대상'으로 지정한 첫 번째 대상만 (정규식의 특징) 변경된다는 점에 유의한다.

:s/show/give

:s/show/take 를 입력하자 커서가 위치한 라인의 show 가 take 로 변경되었다.

 

'찾을 대상'은 사실 정규표현식을 지정하는 곳이다. 다음처럼 입력하면 w 로 시작하는 단어(regex: [w]\w+)'를 모두 'path' 로 변경해준다.

:s/[w]\w+/path

'찾아 바꾸기'에서는 찾을 대상을 정규표현식을 활용하여 지정한다. 여기서도 매칭되는 첫 번째 대상만이 변경되었다.

Vim 매칭되는 모든 대상 변경하기

위에서 살펴보았듯이 정규표현식(찾을 문자열)과 매칭되는 첫 번째 대상만이 변경되는 것을 확인할 수 있다. 문장 'show us the way to wake' 에서 정규표현식 [w]\w+ 에 매칭되는 문자열은 'way' 와 'wake' 인데, 이 둘을 모두 변경하고 싶다면 어떻게 해야할까? 

 

(비추천)Vim 이전 커맨드모드 명령어 재실행 

첫 번째 방법은 이전에 수행한 커맨드모드 명령을 다시 반복하면 된다. Vim 이전 커맨드모드 명령 반복은 @: 이다. 이전 시간([The Vim Way] 13 Vim 기본조작 - 기본조작편 요약 (ft. viEmu Cheat Sheet), Vim 학습 커리큘럼)에 살펴보았듯 @{register} 명령어는 register 에 저장된 매크로 실행인데, : 레지스터는 이전 커맨드모드 명령이 저장되는 곳이다. 따라서, @:는 이전 커맨드모드 명령어를 재실행한다. 다음 스크린샷에서처럼 '이전 커맨드모드 명령어 실행 - @:' 기능을 이용하여 순차적으로 way 를 path 로 변경해보자.

💥 안타깝게도 VSCode Vim 에서는 escaping 문자가 들어간 커맨드모드 명령을 다시 반복하는 데 문제가 있어보인다. (2021년 12월 기준) 사실 커맨드모드 명령을 반복하는 것을 소개하기 위해 이번 장을 '굳이' 추가했다. 

'굳이' 커맨드모드 명령 반복(@:)을 활용하여 찾아바꾸기를 수행해보았다.

사실 '찾아 바꾸기'를 이렇게 사용하는 사용자는 아마 지구상에 존재하지 않을지도 모른다.

 

정규표현식에 global 지정자 추가

 

다음과 같이 정규식의 마지막에 'global' 을 의미하는 g 를 추가하여 '찾을 문자열'과 매칭되는 모든 대상에 대해 바꾸기를 수행할 수 있다. 사실, 라인마다 매칭되는 첫번째 대상만을 변경하는 경우는 거의 없다. 따라서 /g 는 거의 필수로 들어가게 된다.

:s/way/path/g

정규표현식 global 지정자를 이용하여 지정한 정규표현식과 매칭되는 모든 토큰을 변경했다.

Vim 대소문자 관계없이 매칭되는 모든 대상 변경하기

g 뒤에 i 를 추가해준다. 이는 case-insensitive 를 의미한다. 다음 명령의 의미는 '대소문자 관계없이 모든 way 를 path 로 변경하라' 라는 의미가 된다. 

:s/way/path/gi

Vim 범위 지정하여 찾아 바꾸기

  1. Visual Mode 로 범위를 지정
  2. : 로 커맨드모드 진입 -> 자동으로 지정한 범위(:'<,'> 표시)에 대해 커맨드모드 진입
  3. s/{찾을 대상 regex}/{바꿀 문자열} 입력

커맨드모드 명령어는 지정한 범위에 대해서만 적용되도록 할 수 있다. Visual Mode 로 범위를 지정한 뒤 :을 입력하면 상태표시줄에 이전과는 조금 다른 표시가 보인다. '<,'> 표시는 지정된 블록의 시작('<)과 끝('>)을 의미한다. 따라서 해당 명령이 지정된 범위에 대해서만 작동할 것을 암시한다.

범위를 지정하여 바꾸기를 수행한다. 여전히 라인마다 첫 번째 매칭되는 대상만 변경되므로, /g 를 추가하여 매칭되는 모든 대상에 적용했다.

Vim 문서 전체를 대상으로 찾아 바꾸기

  1. ggVG
  2. :s/{찾을 대상 정규표현식}/{바꿀 문자열}/g

ggVG 는 3가지 명령의 조합이다. gg 는 문서의 맨 처음으로, V(= shift + v) 는 Visual Line Mode 진입, G(= shift + g) 는 문서의 맨 끝으로 이동하는 명령이다. 즉, 문서 전체를 선택하여 Visual Mode 로 진입한다. 이후 : 를 입력하면, 지정된 범위를 표현하는 '<,'> 표시와 함께 커맨드모드가 시작된다. 이 때 s/{찾을 대상 정규표현식}/{바꿀 문자열}/g 를 입력하여 바꾸기 명령을 수행할 수 있다. 마찬가지로 명령어의 오른족 끝에 /g 를 붙여 매칭되는 모든 토큰을 변경하겠다는 것을 명시해주었다. 

 

하지만 더 나은 방법이 있다.

Vim 특정 라인을 지정하여 찾아 바꾸기

vim command 모드 명령어는 : 이후에 범위를 지정할 수 있다. 앞선 예제에서 '<,'>의 의미가 각각 블록 범위의 첫 행과 끝 행을 의미했던것과 동일한 원리로 라인 번호를 지정할 수 있다. 아래와 같이 입력하면 3번부터 5번 라인에 대해 나타나는 모든(/g) 'something' 을 'sth' 로 변경할 수 있다.

:3,5s/something/sth/g

또한, 특수한 라인 번호를 지정할 수도 있다. 다음은 vim 커맨드모드 명령을 수행할 대상 라인 번호를 지정할 수 있는 다양한 방법이다.

  • 0 - 첫 라인(1번 라인) 위의 가상의 라인, 사실상 1과 동일하다
  • $ - 마지막 라인, 마지막 라인 넘버는 보통 가변적이므로 이렇게 지정한다.
  • '< - 블록 지정범위의 시작 라인
  • '> - 블록 지정범위의 끝 라인
  • . - 현재 커서가 위치한 라인
  • '{mark} - 해당 mark 가 포함되어있는 라인
💡 이번 연재를 통틀어 mark 를 다루지 않았습니다. mark 는 커서의 위치를 특정 문자에 매핑하여 기억할 수 있는 기능입니다. Vim Normal mode 에서 m 명령 이후 기억할 문자(A~Z, a~z, 0~9)를 입력하면 해당 문자에 현재 위치가 기억됩니다. (:marks 로 기억된 마크들을 모두 확인할 수 있습니다.)

마크된 위치로 `{mark문자}(= 백틱 + 마크기호)로 이동할 수 있으며 해당 라인으로는 `{mark문자}로 이동할 수 있습니다. 가령 Vim Normal Mode 에서 mk 를 입력하면 현재 커서의 위치에 k 라는 마크를 지정하는 것입니다. 이후 'k(= 따옴표 + 마크기호) 로 마크된 위치의 라인으로 이동할 수 있으며, `k
(= 백틱 + 마크기호) 로 마크된 정확한 위치로 이동할 수 있습니다.

'k 가 의미하는 것은 k 마크가 위치한 라인'이 되므로, 굳이 마크를 활용해 라인을 지정하는 것은 커맨드모드에서 범위를 'k를 활용하여 지정하기 위해서입니다.

기회가 된다면, 추후 Vim Marking 에 관하여 포스팅을 하도록 하겠습니다.

 

가령 다음과 같이 써주면 문서의 전체(0 라인부터 $ 라인까지)에 대해서 'something' 을 'sth' 로 변경하는 커맨드모드 명령이 된다.

:0,$s/something/sth/g

% - 커맨드모드 문서 전체 지정자

문서 전체에 대해서라면 범위(시작과 끝)를 지정하는 대신 문서 전체를 지정하는 방법도 있다. 바로 %이다. 다음처럼 사용하면 문서 전체에 대해서 'something'을 'sth' 로 변경한다. 즉, 1,$ 범위의 축약형이 % 이다.

:%s/something/sth/g

Vim Substitution Structure

사실 vim substitute 명령은 다음과 같은 구조로 이루어져있다.

:[range]s/[pattern]/[substitution]/[flags]

[range]

range 는 범위를 의미하며 위에서 살펴보았던 것처럼 , 를 활용하여 특정 라인부터 특정 라인까지를 범위로 지정하거나, 문서 전체(%)를 지정할 수 있었다.

 

[pattern]

pattern 은 정규표현식을 의미한다. 문자열을 그대로 입력할 수도 있지만, 정규표현식이므로 몇몇 문자는 정규표현식의  operator 임에 유의한다.

 

[substitution]

대체할 문자열이다. patten 에 정규식의 그룹을 활용했다면, 여기서 $1, $2 등을 활용해 그룹을 명시할 수 있다.

가령, <code>Hello! World!</code><c>Hello! World!</c> 와 같이 바꾸고 싶다면 다음과 같은 명령어로 가능하다.

:s/<code>(.*)<\/code>/<c>$1<\/c>/g

[flags]

정규표현식의 flags 와 거의 동일하다. 다양한 flags 들이 있지만, g 는 global(모든 대상에 대하여)을 의미하고, i 는 case-insensitive 를 의미한다.

Vim 찾아바꾸기 - 찾기&변경 응용

커맨드모드 명령 substitute 를 활용하여 '찾아 바꾸기'를 수행할 수도 있지만, 모든 변경을 수행하지 않고, 변경할 내용을 순차적으로 확인하면서 변경하는 방법도 존재한다. 

  1. /{pattern} 으로 찾을 문자열을 찾는다.
  2. ciw 명령어를 입력한 뒤 해당 단어를 수정한다.
  3. n 으로 다음 찾을 문자열로 이동한다.
  4. (변경할지 여부를 결정한 뒤) 변경하려면 .으로 해당 문자를 변경(이전 명령 - 3.에서 수행한 - 반복)한다.
  5. 원하는만큼 3. ~ 4. 를 반복한다.

다음 문장에서 특정 will 만 William 으로 변경하는 예제를 스크린샷으로 보도록 하자.

will 을 찾은 뒤, n 으로 will 을 이동하며, 원하는 will 만 William 으로 변경하는 예제


이상으로 Vim 에서 찾아 바꾸기(substitute) 기능을 어떻게 활용할 수 있는지 다양한 방법들과 함께, 그 구조를 살펴보았다. 사실 요즘 IDE 들의 찾아바꾸기 기능은 거의 기초적인 기능이기에 큰 효용이 없을지도 모르는 기능이지만, 만약을 대비해 익혀두는 것 또한 나쁘지 않을 것 같다.

 

다음 포스팅에서는 프로그래머에게 특별히 유용한 surround 기능을 소개한다. 이 기능으로 괄호와 관련된 다양한 동작들을 수행할 수 있다.

반응형

Designed by JB FACTORY