Vimscript 배우기 cho.books
제 6 장 · 원서 §19–22

변수, 조건문, 비교

여기서부터 Vimscript를 진짜 프로그래밍 언어로 보기 시작합니다. 변수와 스코프, if 분기, 그리고 사용자 설정에 따라 동작이 바뀌는 위험한 == 연산자.

let 변수 스코프 if / elseif / else 강제 형변환 ==# / ==?

Section 01변수

지금까지는 단일 명령을 다뤘습니다. 이제 책의 다음 3분의 1 동안, Vimscript를 프로그래밍 언어로 바라봅니다.

가장 먼저 이야기할 것은 변수입니다. 다음 명령을 실행하세요:

vimscript — 변수 선언과 출력
:let foo = "bar"
:echo foo            " bar 출력

:let foo = 42
:echo foo            " 42 출력

let 명령으로 변수를 만들고 값을 대입합니다. foo에 문자열 "bar"를, 그다음 정수 42를 대입했습니다. 변수의 타입이 자유롭게 바뀌는 것처럼 보이지만, Vimscript는 동적 타입 언어가 아닙니다 — 그 미묘함은 뒤에서 다룹니다.

Section 02옵션과 레지스터를 변수로

특수 구문으로 옵션을 변수처럼 읽고 쓸 수 있습니다. 이름 앞에 앰퍼샌드(&)를 붙입니다:

vimscript — 옵션을 변수로
:set textwidth=80
:echo &textwidth                       " 80
:let &textwidth = &textwidth + 10  " 계산해서 설정

:set으로는 리터럴 값 하나만 줄 수 있지만, :let으로 옵션을 변수처럼 다루면 Vimscript의 전체 표현력으로 값을 계산할 수 있습니다. 불리언 옵션은 0(거짓)과 1(참)으로 나타납니다.

레지스터도 변수처럼 읽고 쓸 수 있습니다 — 이름 앞에 @를 붙입니다:

vimscript — 레지스터를 변수로
:let @a = "hello!"     " 레지스터 a 에 텍스트 저장
:echo @"               " 무명 레지스터 (마지막 yank/delete)
:echo @/               " 현재 검색 패턴
직관 @"는 무명 레지스터 — 목적지를 지정하지 않고 복사·삭제한 텍스트가 들어가는 곳입니다. @/는 현재 검색 패턴이라, 프로그램으로 검색 패턴을 읽고 수정할 수 있습니다. 8장의 Grep 연산자에서 이 둘을 실제로 활용합니다.

Section 03변수 스코프

Vimscript는 변수에 한 가지 비틀기를 더합니다 — 스코프(scope)입니다. 두 개의 다른 파일을 분할로 열고, 한쪽에서 다음을 실행하세요:

vimscript — 버퍼-로컬 변수
:let b:hello = "world"
:echo b:hello       " world

다른 버퍼로 옮겨가 :echo b:hello를 다시 실행하면 — Vim이 변수를 찾을 수 없다는 오류를 냅니다. b: 접두사가 변수 hello를 현재 버퍼에만 국한시켰기 때문입니다.

주요 변수 스코프
접두사의미
g:전역 (global)
b:현재 버퍼 한정
w:현재 창 한정
l:함수 로컬
a:함수 인자
v:Vim이 미리 정의한 변수
기억하세요 문자와 콜론으로 시작하는 변수를 보면, 그것은 스코프가 지정된 변수를 뜻합니다. 7장에서 함수를 배우면 a:(인자)와 l:(로컬) 스코프를 본격적으로 활용하게 됩니다.

Section 04조건문

모든 프로그래밍 언어에는 분기 방법이 있고, Vimscript에서 그것은 if 문입니다. 먼저 여러 줄 문법을 알아 둡시다 — 명령들을 파이프(|)로 구분하면 한 줄로 쓸 수 있습니다. 이제 if를 실행해 보세요:

vimscript — 기본 if
:if 1
:  echom "ONE"      " 정수 1은 '참' — 실행됨
:endif

그런데 문자열을 조건에 넣으면 결과가 놀랍습니다:

vimscript — 문자열의 진위
:if "something"     " 출력 없음 — '거짓'으로 취급!
:if "9024"         " 출력됨 — 숫자로 시작하면 그 숫자로 강제 변환
개념 강제 형변환 (coercion)

Vim은 필요할 때 변수와 리터럴을 강제로 변환합니다. "hello" + 1010이 됩니다 — 숫자로 시작하지 않는 문자열은 0으로 변환됩니다.

"10hello" + 1020입니다 — 숫자로 시작하는 문자열은 그 숫자로 변환됩니다.

if는 모든 강제 변환이 끝난 뒤 조건이 0이 아닌 정수면 본문을 실행합니다.

Python처럼 elseelseif도 지원합니다:

vimscript — elseif / else
:if 0
:  echom "if"
:elseif "nope!"
:  echom "elseif"
:else
:  echom "finally!"   " 앞 조건이 모두 거짓 — 실행됨
:endif

Section 05비교 — == 의 위험

if 문은 비교 없이는 쓸모가 적습니다. 숫자 비교는 예상대로 동작합니다. 문자열 비교도 그래 보입니다 — 하지만 함정이 있습니다. 다음을 실행하세요:

vimscript — == 는 사용자 설정에 휘둘린다
:set ignorecase
:if "foo" == "FOO"   " ignorecase가 켜지면 — 참이 된다!
:  echom "설마 했는데..."
:endif
함정 ==의 동작은 사용자의 ignorecase 설정에 따라 달라집니다! 다른 사람이 쓸 플러그인을 만들 때, ==를 절대 신뢰하면 안 됩니다. 맨 ==는 플러그인 코드에 등장해서는 안 됩니다.

해결책 — Vim에는 두 가지 추가 비교 연산자 집합이 있습니다:

vimscript — 명시적 대소문자 비교
:if "foo" ==# "FOO"   " ==# : 설정 무관, 항상 대소문자 구분
:if "foo" ==? "FOO"   " ==? : 설정 무관, 항상 대소문자 무시
철칙 문자열 비교에는 언제나 명시적 연산자를 쓰세요 — 대소문자를 구분하려면 ==#, 무시하려면 ==?. 평범한 ==는 잘못된 코드이며 언젠가 깨집니다. 이것은 "nmap 대 nnoremap"과 같은 교훈입니다 — 사용자 설정을 절대 신뢰하지 마세요.
모션 · == 가 깨지는 자리 01 / 4
스페이스: 재생 · ← →: 단계 이동

Section 06연습문제

  • ~/.vimrcset 명령 일부를 let &option = ... 형식으로 바꿔 보고, 다시 되돌리세요(set으로 충분하면 let을 쓰지 마세요 — 읽기 어렵습니다).
  • :help registers를 읽고, 읽고 쓸 수 있는 레지스터 목록을 살펴보세요.
  • :help internal-variables의 스코프 목록을 훑어보세요.
  • :set ignorecase:set noignorecase를 오가며 다양한 비교가 어떻게 동작하는지 확인하세요.
  • :help expr4를 읽고 사용 가능한 모든 비교 연산자를 확인하세요.
  • :help ignorecase를 읽고 왜 누군가 그 옵션을 켜는지 알아보세요.