Missing \begin{document}

! LaTeX Error: Missing \begin{document}.는 LaTeX 오류 가운데 드물게 자기 자신에 대해 거짓말하는 메시지입니다. 이 오류가 나는 문서 대부분에는 \begin{document}가 멀쩡히 적혀 있습니다. 진상은 이렇습니다. LaTeX은 \begin{document}가 있는지 한 번도 확인하지 않습니다. 확인하는 것은 오직 문단이 시작되었는가뿐이고, 프리앰블에서 문단이 시작되는 순간 이 문구가 나오도록 덫을 걸어 둔 것입니다. 그러니 이 오류는 “\begin{document}가 없다”가 아니라 “\begin{document}보다 앞에서 인쇄될 것이 나타났다”로 읽어야 맞습니다. 이 페이지에서는 그 장치를 LaTeX 본체 소스로 확인한 뒤, 잘못 들어온 본문, 프리앰블에 남은 \maketitle, 그리고 눈에 보이지 않는 1바이트라는 세 갈래 원인을 차례로 살펴봅니다.

이 메시지는 왜 거짓말을 하는가

LaTeX은 프리앰블의 \everypar\@nodocument를 심어 두고, 문단이 시작되는 순간 그것이 터집니다. \everypar는 문단이 시작될 때마다 실행되는 TeX의 갈고리이고, 문단은 “인쇄될 것이 나타난 순간”에 시작됩니다. LaTeX 본체 latex.ltx의 해당 부분에는 개발자들이 남긴 주석이 그대로 붙어 있습니다.

latex.ltx
\gdef\@nodocument{%
  \@latex@error{Missing \protect\begin{document}}\@ehd}

\everypar{\@nodocument} %% To get an error if text appears before the
\nullfont               %% \begin{document}

\begin{document} 앞에 글자가 나타나면 오류를 내려고” — 주석이 대놓고 그렇게 말합니다. \begin{document}가 하는 일은 이 덫을 푸는 것이지, 덫이 \begin{document}를 찾고 있는 것이 아닙니다. 바로 아래의 \nullfont도 같은 발상입니다. 프리앰블 동안에는 “글자가 하나도 없는 폰트”가 현재 폰트라서, 실수로 인쇄되더라도 지면에는 아무것도 남지 않습니다. 고치는 법도 이로써 정해집니다. 찾아야 할 것은 \begin{document}가 아니라 그 앞에 있는, 인쇄되는 무언가입니다. 다만 그 줄이 정말로 있는지, 철자가 맞는지는 한 번만 확인해 두세요. 드물지만 \begin{document}가 실제로 없을 때도 같은 메시지가 납니다. 그 한 번의 확인이 끝나면 더는 그 줄을 들여다볼 필요가 없습니다.

로그는 범인인 한 글자를 그대로 보여 줍니다

l.NN 줄은 원인이 된 그 한 글자 바로 뒤에서 꺾입니다. TeX은 오류가 난 줄을 “읽은 부분”과 “아직 읽지 않은 부분”으로 갈라 위아래로 늘어놓으므로, 윗줄의 끝이 곧 범인입니다. 단어가 중간에 잘린 것처럼 보이는 이유가 여기 있습니다. 아래 예에서는 3행 첫머리의 T가 인쇄될 것으로서 첫 타자가 되어 문단을 시작시켰고, 거기서 덫이 터졌습니다. 이 문서에는 4행에 \begin{document}가 멀쩡히 있습니다.

terminal
! LaTeX Error: Missing \begin{document}.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...

l.3 T
     his line is body text by mistake.

프리앰블에서 “인쇄되는 것”이란 무엇인가

프리앰블에 두어도 되는 것은 값을 “등록”할 뿐 아무것도 출력하지 않는 명령입니다. \usepackage, \title, \author, \date, \newcommand, 각종 설정은 정보를 저장할 뿐이라 문단을 시작하지 않습니다. 반대로 종이 위에 무언가를 놓으려는 것은 모두 이 오류를 냅니다. 경계선은 “본문처럼 보이는가”가 아니라 “출력하는가”이므로, \title{논문 제목}은 안전하고 \maketitle은 위험하다는 언뜻 이상한 구별이 생깁니다.

  • 본문이 섞여 들어감\documentclass 아래에 쓰다 만 문장이 남아 있거나, 주석으로 쓰려던 줄에 %를 빠뜨린 경우입니다. 압도적으로 가장 흔한 원인입니다.
  • \maketitle이 프리앰블에 있음\title 옆에 나란히 써 버리는 실수가 잦습니다. \maketitle\begin{document} 뒤에 둡니다.
  • 글자를 내보내는 매크로를 호출함 — 프리앰블에 \today를 홀로 적었거나, 문자열로 펼쳐지기만 하는 자작 매크로를 쓴 경우입니다. 정의는 안전하고, 호출이 위험합니다.
  • 기호가 하나 삐져나옴\usepackage{amsmath} 바로 뒤의 ~, 닫지 않은 괄호 밖에 남은 글자 등입니다. }가 하나 남는 경우에는 대신 ! Too many } 오류가 납니다.
latex
\documentclass{article}
\title{My Paper}   % safe: records a value, prints nothing
\author{Me}       % safe
\maketitle        % ! LaTeX Error: Missing \begin{document}.
\begin{document}
\maketitle        % this is where it belongs
\end{document}

보이지 않는 1바이트가 원인일 때

프리앰블을 세 번 읽어도 인쇄될 만한 것이 없다면, 범인은 줄바꿈 없는 공백 U+00A0일 가능성이 큽니다. LaTeX의 utf8enc.dfu에는 \DeclareUnicodeCharacter{00A0}{\nobreakspace}라는 줄이 있어 U+00A0을 “인쇄되는 공백”으로 정의합니다. 즉 엄연한 출력이므로 프리앰블에 있으면 문단이 시작되어 버립니다. 게다가 화면에서는 보통 공백과 구별되지 않습니다. 웹 페이지나 PDF에서 복사한 \usepackage 줄, 맥의 Option+Space, 프랑스어 자판 배열 등에서 아주 자연스럽게 섞여 들어옵니다. 이때 l.NN 위의 반쪽 줄은 끝에 아무것도 보이지 않는 특징적인 모습이 됩니다.

문자들어오는 경로프리앰블에 두었을 때의 동작
U+00A0줄바꿈 없는 공백. 웹이나 PDF에서 복사, 맥의 Option+Space, 프랑스어 자판\nobreakspace로 인쇄되므로 이 오류가 납니다. 어느 엔진에서나 같습니다
U+3000전각 공백. CJK 입력기를 켠 채 친 공백이거나 CJK 문장에서 복사한 경우xelatexlualatex에서는 이 오류. pdflatex에서는 Unicode character not set up이라는 다른 오류
U+FEFFBOM. 일부 윈도우 에디터가 UTF-8 저장 시 파일 앞에 붙입니다TeX Live 2024에서는 pdflatexxelatexlualatex 모두 무시합니다. 원인이 아닙니다

표의 마지막 행은 널리 퍼진 조언 하나를 부정합니다. BOM은 이 오류의 원인이 아닙니다. TeX Live 2024에서 시험해 보면, 앞에 BOM이 붙은 파일은 pdflatexxelatexlualatex 어느 쪽에서도 경고조차 없이 통과합니다(file 명령이 UTF-8 (with BOM) text라고 알려 주는 상태에서도 같습니다). “BOM 없이 다시 저장하라”는 조언을 따라도 낫지 않는 이유가 여기 있습니다. 시간을 쓸 바에는 U+00A0을 찾는 편이 맞습니다. 덧붙이자면 \usepackage[utf8]{inputenc}도 이제는 쓸 필요가 없습니다. LaTeX News 28(2018년)에서 UTF-8이 기본 입력 인코딩이 되었습니다. 인코딩 자체를 다루는 내용은 “인코딩과 줄바꿈” 페이지에 있습니다.

섞여 들어온 바이트를 실제로 찾아내기

프리앰블은 원래 전부 ASCII여야 하므로, “ASCII가 아닌 문자를 포함한 줄”을 기계적으로 찾으면 단번에 나옵니다. 다만 문서 전체에 걸면 한국어나 일본어 본문이 모든 줄에서 걸려 쓸모가 없습니다. \begin{document}까지로 잘라 낸 뒤에 찾는 것이 요령입니다. 줄을 특정했으면 sed -n l로 그 줄의 바이트를 봅니다. U+00A0이라면 \302\240으로 표시되어 정체가 확정됩니다. 그다음은 그 두 바이트를 보통 공백으로 다시 치기만 하면 됩니다.

bash
# list preamble lines that contain a non-ASCII character
sed -n '1,/begin{document}/p' paper.tex | LC_ALL=C grep -n '[^ -~]'

# reveal the bytes on a suspect line (U+00A0 shows up as \302\240)
sed -n '3p' paper.tex | sed -n l

# check whether the file carries a byte order mark
file paper.tex