Vim이 안 꺼질 때부터 시작하는 Vi/Vim: 종료, 모드, 이동, 편집, 검색·치환

이 글의 핵심

Vim을 안전하게 종료하는 방법부터 노멀·입력·비주얼 모드, 자주 쓰는 이동과 편집 명령, 검색과 치환, 저장과 종료까지 서버에서 바로 쓰는 명령어를 정리합니다.

들어가며: “Vim을 켰는데 종료가 안 돼요!”

SSH로 서버에 접속하면 GUI 에디터가 없습니다. 이때 Vi/Vim은 거의 모든 리눅스/유닉스 시스템에 기본 설치되어 있는 필수 텍스트 에디터입니다.

하지만 처음 Vim을 열면:

  • 텍스트를 입력할 수 없음
  • 종료 방법을 모름
  • 마우스가 작동하지 않음
  • 명령어가 독특함

긴급 탈출: Vim 종료하기

가장 많이 검색되는 질문

# Vim을 열었는데 종료가 안 될 때
# 1단계: ESC 키를 여러 번 누르기
ESC ESC ESC

# 2단계: 다음 중 하나 입력
:q          # 저장하지 않고 종료 (변경사항 없을 때)
:q!         # 강제 종료 (변경사항 버림)
:wq         # 저장 후 종료
:x          # 저장 후 종료 (변경사항 있을 때만 저장)
ZZ          # :x와 동일 (더 빠름)

상황별 종료 방법

상황명령어설명
아무것도 안 바꿨음:q그냥 종료
수정했는데 저장하고 싶음:wq 또는 ZZ저장 후 종료
수정했는데 버리고 싶음:q!강제 종료
파일명 지정 저장:w filename다른 이름으로 저장
읽기 전용 파일 강제 저장:w!권한 있을 때만 가능

:q가 E37: No write since last change (add ! to override)로 거부되면 “수정한 내용이 있다”는 뜻이므로, 저장할지(:wq) 버릴지(:q!)를 정하면 됩니다. 창을 여러 개 열었거나 vim a.txt b.txt처럼 파일을 여러 개 열었다면 :q가 현재 창만 닫으므로, 한 번에 모두 끝내려면 :qa(모두 종료), :wqa(모두 저장 후 종료), :qa!(모두 버리고 종료)를 씁니다.

종료가 안 되는 원인이 Vim이 아닌 경우도 있습니다. 습관처럼 Ctrl+S를 누르면 터미널이 출력 정지(XOFF) 상태가 되어 어떤 키를 눌러도 화면이 멈춘 것처럼 보이는데, Vim이 멈춘 게 아니라 터미널이 출력을 막고 있는 것이므로 Ctrl+Q를 누르면 풀립니다. 또 실수로 Ctrl+Z를 누르면 Vim이 백그라운드로 정지되어 셸 프롬프트로 돌아오는데, 이때 파일은 아직 열린 상태이므로 fg로 다시 불러와 정상 종료해야 합니다. 그냥 새로 vim을 열면 다음 절의 스왑 파일 경고를 보게 됩니다.

”E325: ATTENTION” 스왑 파일 경고

Vim은 편집 중인 내용을 .파일명.swp 스왑 파일에 계속 기록해, SSH 연결이 끊기거나 Vim이 비정상 종료되어도 복구할 수 있게 합니다. 그래서 이전 세션이 제대로 끝나지 않았거나 다른 사람(또는 다른 터미널의 나)이 같은 파일을 열고 있으면 파일을 열 때 E325: ATTENTION 경고와 함께 [O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort 선택지가 나옵니다. 서버에서 설정 파일을 수정할 때 이 경고를 그냥 E로 넘기면 다른 쪽의 수정 내용을 덮어쓸 수 있으므로, 먼저 다른 세션에서 같은 파일을 열고 있지 않은지 확인하는 것이 안전합니다. 이전 세션이 끊긴 것이 확실하면 R로 복구한 뒤 내용을 확인하고 저장하고, 복구가 필요 없으면 D로 스왑 파일을 지우면 됩니다. 연결이 불안정한 서버에서 긴 편집을 할 때 이 기능 덕분에 작업을 날리지 않은 경험이 있어서, 저는 스왑 파일을 끄지 않고 대신 set directory=~/.vim/swap//로 모아 두는 쪽을 택합니다.


Vi vs Vim 차이

Vi (Visual Editor)

  • 1976년 개발 (거의 50년 전!)
  • 모든 유닉스/리눅스에 기본 설치
  • 최소한의 기능

Vim (Vi IMproved)

  • 1991년 개발 (Vi의 개선판)
  • 추가 기능:
    • 문법 강조 (Syntax Highlighting)
    • 다중 취소/재실행
    • 플러그인 시스템
    • 분할 화면
    • 자동완성
    • 비주얼 모드

확인 방법

# Vi 버전 확인
vi --version

# Vim 설치 확인
vim --version

# 대부분의 시스템에서 vi는 vim의 심볼릭 링크
ls -l $(which vi)
# lrwxrwxrwx 1 root root 20 /usr/bin/vi -> /etc/alternatives/vi

모드 이해하기

Vim의 핵심: 모드

Vim은 모드 기반 에디터입니다. 같은 키가 모드에 따라 다르게 동작합니다.

graph LR
    N[Normal 모드] -->|i, a, o| I[Insert 모드]
    I -->|ESC| N
    N -->|v, V, Ctrl+v| V[Visual 모드]
    V -->|ESC| N
    N -->|:| C[Command 모드]
    C -->|Enter| N

Normal 모드 (기본)

  • Vim을 열면 이 모드
  • 명령어 실행 (이동, 삭제, 복사 등)
  • 텍스트 입력 불가
  • ESC로 언제든 돌아옴
# Normal 모드 명령어 예시
dd    # 현재 줄 삭제
yy    # 현재 줄 복사
p     # 붙여넣기
u     # 실행 취소

Insert 모드 (입력)

  • 텍스트 입력 모드
  • 일반 에디터처럼 타이핑 가능
  • ESC로 Normal 모드로 복귀
# Insert 모드 진입 명령어
i     # 커서 위치에서 입력
I     # 줄 맨 앞에서 입력
a     # 커서 다음 칸에서 입력
A     # 줄 맨 뒤에서 입력
o     # 아래 새 줄에서 입력
O     # 위 새 줄에서 입력

Visual 모드 (선택)

  • 텍스트 선택 모드
  • 블록 지정 후 복사/삭제
v       # 문자 단위 선택
V       # 줄 단위 선택
Ctrl+v  # 블록 단위 선택 (세로 선택)

Command 모드 (명령)

  • : 로 시작
  • 저장, 종료, 검색, 치환 등
:w      # 저장
:q      # 종료
:wq     # 저장 후 종료
:%s/old/new/g  # 치환

기본 이동 명령어

방향키 대신 hjkl

# 기본 이동 (화살표 키도 작동하지만 hjkl이 더 빠름)
h    # ← 왼쪽
j    # ↓ 아래
k    # ↑ 위
l    # → 오른쪽

왜 hjkl?: Bill Joy가 Vi를 만들 때 쓰던 ADM-3A 터미널에는 별도의 화살표 키가 없었고, 대신 H·J·K·L 키 위에 화살표가 인쇄되어 있었습니다. 그 배치가 그대로 굳은 것입니다. 지금도 hjkl을 쓰는 이유는 손을 홈 row에서 떼지 않고 이동할 수 있고, 모든 이동 명령이 5j(5줄 아래), d3w(세 단어 삭제)처럼 횟수와 연산자와 조합되기 때문입니다. 화살표 키도 동작하지만 이 조합 문법에 익숙해지는 것이 Vim을 빠르게 쓰는 핵심입니다.

단어 단위 이동

w    # 다음 단어 시작
W    # 다음 단어 시작 (공백 기준)
e    # 다음 단어 끝
E    # 다음 단어 끝 (공백 기준)
b    # 이전 단어 시작
B    # 이전 단어 시작 (공백 기준)

줄 단위 이동

0    # 줄 맨 앞
^    # 줄 맨 앞 (공백 제외)
$    # 줄 맨 뒤
gg   # 파일 맨 앞
G    # 파일 맨 뒤
:n   # n번째 줄로 이동 (예: :42)
42G  # 42번째 줄로 이동

w와 W의 차이는 “단어”의 정의입니다. 소문자 w는 영숫자·밑줄 묶음과 구두점을 별개의 단어로 보므로 config.server.port에서 w를 누르면 .에서도 멈추고, 대문자 W는 공백으로만 나누므로 한 번에 다음 공백 뒤로 건너뜁니다. 경로나 URL처럼 기호가 많은 텍스트에서는 대문자 쪽이 훨씬 빠릅니다. 0과 ^의 차이도 들여쓰기된 코드에서 중요해서, 들여쓴 줄의 실제 코드 시작으로 가려면 ^를 씁니다.

화면 단위 이동

Ctrl+f   # 한 화면 아래 (forward)
Ctrl+b   # 한 화면 위 (backward)
Ctrl+d   # 반 화면 아래 (down)
Ctrl+u   # 반 화면 위 (up)
H        # 화면 맨 위 (High)
M        # 화면 중간 (Middle)
L        # 화면 맨 아래 (Low)

텍스트 편집

삭제

x     # 커서 위치 문자 삭제
X     # 커서 앞 문자 삭제
dd    # 현재 줄 삭제
D     # 커서부터 줄 끝까지 삭제
dw    # 단어 삭제
d$    # 커서부터 줄 끝까지 삭제
d0    # 커서부터 줄 앞까지 삭제
dG    # 커서부터 파일 끝까지 삭제
dgg   # 커서부터 파일 앞까지 삭제

변경

r     # 커서 위치 문자 교체 (1글자)
R     # 덮어쓰기 모드
cw    # 단어 변경 (change word)
cc    # 줄 변경
C     # 커서부터 줄 끝까지 변경
s     # 문자 삭제 후 입력 모드
S     # 줄 삭제 후 입력 모드

Vim 편집 명령의 규칙은 연산자 + 이동(motion)입니다. d(삭제), c(변경), y(복사)는 연산자이고, 뒤에 붙는 w, $, G, gg 같은 이동 명령이 적용 범위를 정합니다. 그래서 dw, d$, dG를 따로 외울 필요 없이 이동 명령을 알면 조합이 생깁니다. 여기에 iw(단어 안쪽), i"(따옴표 안쪽), i((괄호 안쪽) 같은 텍스트 객체를 붙이면 ci"로 커서가 문자열 중간에 있어도 따옴표 안 내용 전체를 바꿀 수 있습니다. 설정 파일의 값을 바꿀 때 가장 자주 쓰는 조합입니다. 연산자를 두 번 쓰면(dd, cc, yy) 현재 줄 전체가 대상입니다.

취소/재실행

u        # 실행 취소 (undo)
Ctrl+r   # 재실행 (redo)
.        # 마지막 명령 반복

검색과 치환

검색

/pattern    # 앞으로 검색
?pattern    # 뒤로 검색
n           # 다음 검색 결과
N           # 이전 검색 결과
*           # 커서 위치 단어 검색
#           # 커서 위치 단어 역방향 검색

예시:

/error      # "error" 검색
n           # 다음 error로 이동
N           # 이전 error로 이동

치환

:s/old/new/        # 현재 줄에서 첫 번째 old를 new로
:s/old/new/g       # 현재 줄에서 모든 old를 new로
:%s/old/new/g      # 전체 파일에서 모든 old를 new로
:%s/old/new/gc     # 전체 파일, 확인하며 치환
:5,10s/old/new/g   # 5~10번째 줄에서 치환

실전 예시:

# Python에서 print 문을 logging으로 변경
:%s/print(/logging.info(/g

# 탭을 4개 공백으로 변경
:%s/\t/    /g

# 줄 끝 공백 제거
:%s/\s\+$//g

치환 패턴은 기본적으로 Vim 정규식이라 +, (, | 같은 기호가 이스케이프해야 특수 의미를 가집니다. 그래서 줄 끝 공백 제거에 \s\+처럼 백슬래시가 붙습니다. 패턴 앞에 \v(very magic)를 붙이면 :%s/\v\s+$//처럼 일반 정규식에 가깝게 쓸 수 있습니다. 경로처럼 /가 많은 문자열을 바꿀 때는 구분자를 바꿀 수 있어서 :%s#/usr/local#/opt#g처럼 쓰면 \/를 반복할 필요가 없습니다. 또 :%s/oldFunc/newFunc/g는 oldFuncHelper 같은 다른 이름의 일부도 바꿔 버리므로, 단어 전체만 바꾸려면 \<oldFunc\>처럼 단어 경계를 지정해야 합니다. 대소문자 구분은 .vimrc의 ignorecase/smartcase 설정을 따르며, 한 번만 바꾸려면 패턴 끝에 \C(구분)나 \c(무시)를 붙입니다.


복사, 붙여넣기, 삭제

복사 (Yank)

yy    # 현재 줄 복사
Y     # 현재 줄 복사 (yy와 동일)
yw    # 단어 복사
y$    # 커서부터 줄 끝까지 복사
yG    # 커서부터 파일 끝까지 복사

붙여넣기 (Paste)

p     # 커서 뒤에 붙여넣기
P     # 커서 앞에 붙여넣기

dd나 x로 지운 내용도 레지스터에 저장되므로, dd 후 p를 누르면 방금 지운 줄이 붙여넣어집니다. 이것이 Vim에서 “잘라내기”가 따로 없는 이유이자, yy로 복사해 둔 내용이 중간에 dd를 하는 순간 덮어써지는 흔한 당혹의 원인입니다. 복사한 내용은 "0 레지스터에도 따로 남아 있으므로 "0p로 붙여넣으면 이 문제를 피할 수 있습니다. 참고로 Neovim에서는 Y가 yy가 아니라 y$(커서부터 줄 끝까지)로 기본 매핑이 바뀌었습니다.

Visual 모드로 복사

# 1. v로 Visual 모드 진입
# 2. hjkl로 선택 범위 지정
# 3. y로 복사 (또는 d로 잘라내기)
# 4. p로 붙여넣기

예시:

# 5줄 복사하기
5yy   # 현재 줄부터 5줄 복사
p     # 붙여넣기

# 블록 복사
Ctrl+v   # 블록 선택 모드
jjjj     # 아래로 4줄 선택
lll      # 오른쪽으로 3칸 선택
y        # 복사
p        # 붙여넣기

저장과 종료

저장

:w              # 저장
:w filename     # 다른 이름으로 저장
:w!             # 강제 저장 (읽기 전용 파일)
:w >> file      # 파일 끝에 추가
:5,10w file     # 5~10번째 줄만 저장

종료

:q      # 종료 (변경사항 없을 때)
:q!     # 강제 종료 (변경사항 버림)
:wq     # 저장 후 종료
:x      # 저장 후 종료 (변경사항 있을 때만)
ZZ      # :x와 동일 (더 빠름)
ZQ      # :q!와 동일

분할 화면

수평/수직 분할

:split filename    # 수평 분할
:sp filename       # 수평 분할 (축약)
:vsplit filename   # 수직 분할
:vsp filename      # 수직 분할 (축약)

# 빈 창 분할
:split    # 수평
:vsplit   # 수직

창 간 이동

Ctrl+w h   # 왼쪽 창
Ctrl+w j   # 아래 창
Ctrl+w k   # 위 창
Ctrl+w l   # 오른쪽 창
Ctrl+w w   # 다음 창

창 크기 조절

Ctrl+w =   # 모든 창 크기 동일
Ctrl+w +   # 현재 창 높이 증가
Ctrl+w -   # 현재 창 높이 감소
Ctrl+w >   # 현재 창 너비 증가
Ctrl+w <   # 현재 창 너비 감소

필수 설정 (.vimrc)

.vimrc 파일 생성

# 홈 디렉토리에 .vimrc 생성
vim ~/.vimrc

.vimrc를 만들면 사라지는 기본값

설정 파일이 하나도 없을 때 Vim은 defaults.vim을 읽어 incsearch, wildmenu, scrolloff=5, 안전한 backspace 같은 합리적인 기본값을 켭니다. 그런데 ~/.vimrc를 만드는 순간 이 파일은 읽히지 않습니다. 같은 Ubuntu 24.04(Vim 9.1)에서 세 경우를 비교한 결과입니다.

상황incsearchscrolloffwildmenu
~/.vimrc 없음켜짐5켜짐
빈 ~/.vimrc만 만듦꺼짐0꺼짐
~/.vimrc 첫 줄에 source $VIMRUNTIME/defaults.vim켜짐5켜짐

그래서 “.vimrc를 만들었더니 검색이 입력하는 동안 안 따라온다”, “화면 맨 위·아래 줄에 커서가 붙는다” 같은 변화가 생깁니다. 아래 추천 설정은 필요한 항목을 직접 적어 두었지만, 기본값을 그대로 쓰면서 몇 개만 바꾸고 싶다면 .vimrc 맨 위에 다음 두 줄을 두고 그 아래에 바꿀 설정만 쓰는 편이 짧습니다.

source $VIMRUNTIME/defaults.vim   " 기본값을 먼저 불러오고
set number                        " 바꾸고 싶은 것만 아래에 쓴다

설정 파일의 위치는 ~/.vimrc 말고 ~/.vim/vimrc도 됩니다. 둘 중 하나만 읽히며, 어느 파일이 실제로 쓰였는지는 Vim 안에서 :echo $MYVIMRC로 확인합니다(~/.vim/vimrc만 있는 환경에서 그 경로가 출력되는 것을 확인했습니다).

추천 기본 설정

" 기본 설정
set nocompatible        " Vi 호환 모드 끄기
syntax on               " 문법 강조
set number              " 줄 번호 표시
set relativenumber      " 상대 줄 번호
set cursorline          " 커서 줄 강조
set ruler               " 커서 위치 표시

" 들여쓰기
set autoindent          " 자동 들여쓰기
set smartindent         " 스마트 들여쓰기
set tabstop=4           " 탭 크기
set shiftwidth=4        " 자동 들여쓰기 크기
set expandtab           " 탭을 공백으로 변환

" 검색
set hlsearch            " 검색 결과 강조
set incsearch           " 점진적 검색
set ignorecase          " 대소문자 무시
set smartcase           " 대문자 포함 시 대소문자 구분

" UI
set showmatch           " 괄호 매칭 표시
set wildmenu            " 명령어 자동완성
set laststatus=2        " 상태바 항상 표시
set showcmd             " 명령어 표시

" 편의 기능
set mouse=a             " 마우스 사용
set clipboard=unnamed   " 시스템 클립보드 사용
set encoding=utf-8      " UTF-8 인코딩
set backspace=indent,eol,start  " 백스페이스 동작

" 파일 타입별 설정
filetype plugin indent on

" 색상 테마
colorscheme desert      " 색상 테마 (기본 제공)

몇 가지 설정은 서버 환경에서 기대와 다르게 동작합니다. set clipboard=unnamed는 Vim이 +clipboard 기능으로 빌드되어 있어야 하는데, 서버의 기본 vim이나 vim-tiny는 대부분 -clipboard라서 설정해도 효과가 없습니다(vim --version | grep clipboard로 확인). 게다가 SSH로 접속한 원격 Vim의 “시스템 클립보드”는 서버 쪽 클립보드라서, 내 PC로 복사되지 않습니다. 또 unnamed는 * 레지스터(X11에서는 마우스 가운데 버튼용 PRIMARY 선택)에 연결되고, Ctrl+V로 붙여넣는 일반 클립보드는 + 레지스터라서 리눅스 데스크톱에서는 set clipboard=unnamedplus를 써야 합니다(macOS와 Windows는 unnamed로 충분합니다. 이 점은 Vim 공식 문서 기준이고, 이 서버의 Vim은 -clipboard라 직접 확인하지는 못했습니다). set mouse=a는 편리하지만 켜면 터미널 프로그램의 마우스 드래그 복사가 Vim에 가로채이므로, 원격에서 텍스트를 복사할 일이 많다면 끄거나 Shift를 누른 채 드래그해야 합니다. smartindent는 C 계열 기준의 오래된 규칙입니다. filetype plugin indent on이 켜져 있으면 Python·셸처럼 들여쓰기 스크립트(indentexpr)가 있는 파일 타입에서는 그쪽이 우선하므로 smartindent가 아무 일도 하지 않습니다(:help 'indentexpr'에도 smartindent와 cindent보다 우선한다고 적혀 있습니다). 영향이 남는 곳은 들여쓰기 스크립트가 없는 파일 타입(예: conf)이고, 거기서는 #로 시작하는 줄을 열 0으로 끌어당깁니다. 4칸 들여쓴 줄에서 o로 새 줄을 열고 # comment를 입력했을 때 conf는 # comment(열 0), Python과 셸은 # comment로 들여쓰기가 유지되는 것을 확인했습니다. 그래서 smartindent는 빼고 autoindent와 filetype plugin indent on만 두는 구성이 안전합니다.

언어별 설정

" Python
autocmd FileType python setlocal tabstop=4 shiftwidth=4 expandtab

" JavaScript
autocmd FileType javascript setlocal tabstop=2 shiftwidth=2 expandtab

" Go
autocmd FileType go setlocal tabstop=4 shiftwidth=4 noexpandtab

" Makefile (탭 필수)
autocmd FileType make setlocal noexpandtab

설정이 읽혔는지 확인하기

.vimrc를 고쳤는데 화면이 그대로라면 설정 파일이 안 읽힌 것인지, 읽혔지만 옵션이 안 먹은 것인지부터 가른다. Vim 안에서 다음 세 가지를 확인한다.

:scriptnames                  " 실제로 로드된 vimrc와 스크립트 목록
:echo exists("g:syntax_on")   " 1이면 문법 강조가 켜져 있다
:echo &filetype               " cpp, python처럼 파일 타입이 잡혀야 한다

:scriptnames 맨 위에 ~/.vimrc가 보이지 않으면 파일 위치나 이름이 틀린 것이다. 문법 강조는 syntax on, 파일 타입별 들여쓰기와 플러그인은 filetype plugin indent on이 켠다. 두 줄을 .vimrc에 명시해 두면 배포판 기본값에 의존하지 않는다. 트루컬러를 지원하는 터미널이라면 set termguicolors를 더하고, 테마는 colorscheme habamax처럼 Vim 9에 들어 있는 것 중에서 고른다(:colorscheme <Tab>으로 목록이 나온다).

이 옵션은 누가 바꿨나: :verbose set

값이 기대와 다를 때는 옵션 이름 앞에 :verbose를 붙여 마지막으로 그 값을 정한 파일과 줄을 봅니다.

:verbose set scrolloff? expandtab? autoindent?
  scrolloff=5
	Last set from /usr/share/vim/vim91/defaults.vim line 52
  autoindent
	Last set from /usr/share/vim/vim91/indent/python.vim line 16

위는 Python 파일에서 확인한 출력입니다. autoindent를 .vimrc에 안 적었는데 켜져 있었다면, 파일 타입 플러그인(indent/python.vim)이 켠 것입니다. “분명히 껐는데 다시 켜진다”는 증상은 대개 ftplugin이 파일을 열 때 덮어쓰는 경우라서, 그 파일 타입에만 autocmd FileType python setlocal noexpandtab처럼 지정하거나 ~/.vim/after/ftplugin/python.vim에 넣습니다.

닫았다 열어도 u가 되게: 영구 undo

기본적으로 undo 기록은 Vim을 닫으면 사라집니다. undofile을 켜면 기록이 파일로 남아, 어제 편집한 파일을 다시 열어도 u로 되돌릴 수 있습니다.

set undofile
set undodir=~/.vim/undo   " 이 디렉터리를 미리 만들어 둬야 한다: mkdir -p ~/.vim/undo

동작을 직접 확인한 결과는 이렇습니다. 파일에 two, three를 차례로 추가해 저장하고 Vim을 닫은 뒤 다시 열어 u를 누르면, undofile을 켠 쪽은 three가 사라지고 꺼 둔 쪽은 아무 일도 일어나지 않았습니다. 주의할 점이 두 가지 있습니다. undodir를 지정하지 않으면 기록이 편집한 파일 옆에 .파일명.un~으로 쌓입니다(저장소에 섞여 들어가기 쉽습니다). 반대로 undodir에 없는 디렉터리를 적으면 Vim이 디렉터리를 만들어 주지 않고, 에러 없이 기록만 남기지 않습니다. 이 동작은 +persistent_undo로 빌드된 Vim에서만 됩니다(vim --version | grep persistent_undo).

상대 줄 번호 읽는 법

set number relativenumber를 함께 켜면 커서가 있는 줄은 실제 줄 번호, 나머지 줄은 커서에서 몇 줄 떨어져 있는지가 표시된다.

  3   foo();
  2   bar();
  1   baz();
 42   qux();   <- 커서
  1   a();
  2   b();

이동과 편집 명령 앞에 숫자를 붙일 때 줄 번호를 계산하지 않고 화면의 숫자를 그대로 쓰기 위한 설정이다.

입력동작
5j / 3k5줄 아래 / 3줄 위로 이동
d4j현재 줄과 아래 4줄 삭제
y2k현재 줄과 위 2줄 복사
>3j현재 줄과 아래 3줄 들여쓰기
42G 또는 :42절대 줄 번호로 이동 (컴파일러 에러의 줄 번호로 갈 때)

컴파일러가 알려 주는 절대 줄 번호를 자주 봐야 한다면, 입력 모드에서만 상대 번호를 끄는 방식도 있다.

autocmd InsertEnter,FocusLost * set norelativenumber
autocmd InsertLeave,FocusGained * set relativenumber

플러그인 없이 쓰는 번들 패키지

Vim 9.1 배포본에는 $VIMRUNTIME/pack/dist/opt/ 아래에 선택 설치용 패키지가 들어 있다. 아래는 Vim 9.1(패치 1752, macOS)에서 확인한 목록의 일부다. 더 오래된 버전에는 comment나 editorconfig가 없을 수 있으니 ls $VIMRUNTIME/pack/dist/opt로 먼저 본다.

packadd! matchit       " % 로 if/else/endif, HTML 태그 쌍 이동
packadd! comment       " gcc 로 한 줄 주석 토글, gc{모션} 으로 범위 주석
packadd! editorconfig  " 프로젝트의 .editorconfig 를 읽어 들여쓰기 규칙 적용

editorconfig를 켜면 저장소의 .editorconfig에 적힌 indent_size, end_of_line, trim_trailing_whitespace가 Vim 설정보다 우선해서 적용되므로, 팀 저장소에서 .vimrc와 규칙이 어긋나는 일을 줄일 수 있다.

프로젝트마다 다른 설정 (exrc)

빌드 명령이나 단축키는 프로젝트마다 다르다. 전역 ~/.vimrc에 아래를 두면 Vim을 연 디렉터리의 .vimrc도 함께 읽는다.

set exrc     " 현재 디렉터리의 .vimrc 도 로드
set secure   " 그 파일 안에서는 셸 실행 등 위험한 명령을 막는다

주의할 점이 두 가지 있다. 이 설정은 어느 저장소에서든 그 폴더의 .vimrc를 읽으므로, 내려받은 코드를 열 때는 .vimrc가 있는지 먼저 본다. 그리고 vim -u ~/.vimrc처럼 vimrc를 직접 지정해 실행하면 현재 디렉터리의 .vimrc는 읽히지 않는다(Vim 9.1에서 확인). 평소처럼 vim만 실행해서 확인해야 한다.

빌드 에러를 quickfix로 연결하기

:make는 makeprg에 지정한 명령을 실행하고 출력에서 파일:줄:열: error: 형식을 찾아 quickfix 목록으로 만든다. 빌드가 Docker 컨테이너 안에서 돌면 에러 경로가 /workspace/src/main.cpp처럼 컨테이너 기준이라 호스트의 Vim이 파일을 열지 못한다. 출력의 경로만 호스트 경로로 바꿔 주면 된다.

" 프로젝트의 .vimrc
let s:root = getcwd()
let &makeprg = "./build.sh 2>&1 | sed 's#/workspace/#" . s:root . "/#g'"
set errorformat=%f:%l:%c:\ %t%*[a-z]:\ %m,%f:%l:\ %t%*[a-z]:\ %m,%-G%.%#
nnoremap <leader>m :make<CR>

let &makeprg로 값을 넣을 때는 파이프를 | 그대로 쓴다. :set makeprg=...에서 쓰던 습관대로 \|로 적으면 백슬래시가 그대로 셸에 전달되어 |가 파이프가 아닌 인자가 된다. errorformat의 마지막 %-G%.%#는 앞의 두 패턴에 맞지 않는 줄(ninja: build stopped 같은 것)을 목록에서 뺀다.

실제로 빌드하지 않고 패턴만 확인하려면 :cgetexpr에 한 줄을 넣어 본다.

:cgetexpr ['src/main.cpp:12:3: error: boo']
:copen

항목이 12행 3열로 잡히면 패턴이 맞는 것이다. 이후 :cnext/:cprev로 에러 사이를 이동한다. 자주 쓰면 nnoremap ]q :cnext<CR>, nnoremap [q :cprev<CR>로 매핑해 둔다.


치트시트

모드 전환

명령어동작
ESCNormal 모드로
iInsert 모드 (커서 위치)
aInsert 모드 (커서 다음)
oInsert 모드 (아래 새 줄)
vVisual 모드
:Command 모드

필수 명령어 Top 20

명령어동작사용 빈도
:wq저장 후 종료매우 높음
:q!강제 종료매우 높음
dd줄 삭제매우 높음
yy줄 복사높음
p붙여넣기매우 높음
u실행 취소매우 높음
/pattern검색높음
:%s/old/new/g전체 치환높음
gg파일 맨 앞보통
G파일 맨 뒤보통
0줄 맨 앞보통
$줄 맨 뒤보통
dw단어 삭제보통
cw단어 변경보통
A줄 끝에서 입력보통
o아래 새 줄보통
Ctrl+f한 화면 아래낮음
Ctrl+b한 화면 위낮음
.마지막 명령 반복보통
*단어 검색낮음

실전 시나리오

시나리오 1: 설정 파일 수정

# 1. 파일 열기
vim /etc/nginx/nginx.conf

# 2. 특정 줄 찾기 (커서는 "listen" 단어의 첫 글자로 이동)
/listen

# 3. 포트 번호로 이동 후 변경 (listen 80; → listen 8080;)
w           # 다음 단어(80)로 이동
cw8080<ESC> # 80을 8080으로 변경 (cw는 ;앞에서 멈춤)

# 4. 저장 후 종료
:wq

/listen으로 검색하면 커서는 줄의 맨 앞이 아니라 일치한 단어의 첫 글자에 놓입니다. 그 상태에서 바로 cw를 누르면 포트가 아니라 listen이라는 단어가 바뀌므로, w로 한 단어 옮긴 뒤 바꿔야 합니다. nginx.conf처럼 root 소유 파일을 일반 사용자로 열었다가 수정 후에 E45: 'readonly' option is set 또는 E212: Can't open file for writing을 만나는 일도 흔합니다. :w!는 읽기 전용 표시를 무시할 뿐 파일 권한을 넘지 못하므로, 이때는 :w !sudo tee % > /dev/null로 저장하거나, 애초에 sudoedit /etc/nginx/nginx.conf로 여는 것이 안전합니다. 수정한 뒤에는 nginx -t로 설정 문법을 검사하고 나서 reload하는 습관이 중요합니다.

시나리오 2: 로그 파일 분석

# 1. 로그 파일 열기
vim /var/log/app.log

# 2. 에러 검색
/ERROR

# 3. 다음 에러로 이동
n

# 4. 에러 줄 복사
yy

# 5. 종료 (저장 안 함)
:q

수백 MB짜리 로그를 Vim으로 열면 파일 전체를 메모리에 읽고 스왑 파일까지 만들기 때문에 열리는 데 오래 걸리고 메모리를 크게 씁니다. 읽기만 할 거라면 view(읽기 전용 Vim)나 less를 쓰고, 에러 줄만 보려면 grep ERROR /var/log/app.log | less처럼 먼저 걸러 내는 편이 서버에 부담이 적습니다. 현재 기록 중인 로그를 Vim으로 열어 두면 새로 추가되는 줄이 보이지 않는다는 점도 기억해 두세요. 실시간으로 봐야 하면 tail -f나 less +F가 맞습니다.

시나리오 3: 코드 리팩토링

# 1. 파일 열기
vim app.py

# 2. 함수명 변경 (oldFunc → newFunc)
:%s/oldFunc/newFunc/g

# 3. 확인
:noh  # 검색 강조 제거

# 4. 저장
:w

붙여넣기 들여쓰기가 계단처럼 밀릴 때

외부에서 복사한 코드를 Insert 모드에서 터미널 붙여넣기(우클릭, Ctrl+Shift+V)로 넣으면 줄마다 들여쓰기가 계단처럼 누적되는 경우가 있습니다. 터미널은 붙여넣은 텍스트를 사람이 한 글자씩 타이핑한 것처럼 Vim에 보내고, Vim은 줄바꿈마다 autoindent를 적용하기 때문입니다. Vim 8 이상은 bracketed paste를 지원해 대부분 자동으로 처리되지만, 오래된 Vim이나 일부 터미널에서는 여전히 발생하므로 붙여넣기 전에 :set paste, 끝난 뒤 :set nopaste를 쓰면 됩니다. 클립보드 레지스터를 지원하도록 빌드된 Vim이라면(vim --version에 +clipboard) Normal 모드에서 "+p로 붙여넣는 방법도 이 문제를 피합니다.


더 익히려면

  1. 플러그인 매니저 설치: vim-plug, Vundle
  2. 인기 플러그인:
    • NERDTree (파일 탐색기)
    • fzf.vim (파일 검색)
    • vim-airline (상태바)
    • YouCompleteMe (자동완성)
  3. Neovim 고려: Vim의 현대적 포크

대안 에디터

  • Nano: 더 쉬운 터미널 에디터
  • Emacs: Vim의 라이벌
  • VSCode + Vim 플러그인: GUI + Vim 키바인딩

자주 묻는 질문 (FAQ)

파일 전체를 치환할 때 실수를 줄이려면 어떻게 하나요?

:%s/old/new/g는 파일 전체의 모든 일치 항목을 한 번에 바꾸므로, 확신이 없다면 끝에 c 플래그를 붙인 :%s/old/new/gc로 하나씩 확인하며 치환하는 것이 안전합니다. 특정 구간만 바꿔야 한다면 :5,10s/old/new/g처럼 줄 범위를 지정합니다. 치환 전에 /old로 먼저 검색해 어떤 부분이 걸리는지 확인해 두고, 결과가 잘못됐다면 Normal 모드에서 u로 되돌릴 수 있습니다.