LaTeX 오류 메시지가 읽기 어려운 이유는 그것이 스택 트레이스가 아니기 때문입니다. ! Undefined control sequence 아래에 겹쳐 나오는 두 줄은 어떤 명령이 잘못됐는지에 대한 설명이 아니라, TeX의 읽기 헤드가 멈춘 순간을 찍은 사진입니다. 위쪽 줄은 이미 읽은 부분, 아래쪽 줄은 아직 읽지 않은 부분이고, 그 사이의 끊어진 자리가 바로 사고 현장입니다. 이 점만 이해하면 l.NN 행 번호가 가끔 거짓말을 하는 이유도, ? 프롬프트에서 h와 x 중 무엇을 눌러야 하는지도, .log에 터미널보다 많은 내용이 남는 이유도 모두 같은 하나의 구조로 설명됩니다. 이 페이지는 오류 메시지의 구조, -file-line-error, -interaction의 네 가지 모드, 로그 읽는 법, 그리고 이분 탐색으로 원인을 좁히는 방법을 다룹니다.
오류 메시지의 구조: ! 줄과 두 줄로 겹쳐진 l.NN
! 줄은 무슨 일이 일어났는지를, l.NN으로 시작하는 두 줄은 어디서 멈췄는지를 말합니다. 그리고 원인은 거의 언제나 윗줄의 오른쪽 끝에 있습니다. TeX은 입력 줄을 읽은 부분과 읽지 않은 부분으로 자른 뒤 위아래로 쌓고, 자른 자리를 들여쓰기로 표시합니다. 아래 예에서 윗줄의 끝은 \textbnf이며, 읽는 순간 터진 명령이 바로 그것입니다. {bold} text.는 아직 손대지 않았으므로 아랫줄에 남아 있습니다. 이 자른 자리는 행 번호보다 훨씬 믿을 만합니다. 번호는 TeX이 알아차린 곳이고, 자른 자리는 TeX이 있던 곳이기 때문입니다.
! Undefined control sequence.
l.3 This is \textbnf
{bold} text.
? l.NN 위에 다른 줄이 끼어들 때가 있는데, 이것이 오류의 문맥입니다. \mynorm #1->\lVert처럼 ->가 들어간 줄은 그 매크로를 전개하던 도중에 사고가 났다는 뜻이고, <inserted text>는 TeX이 복구를 위해 스스로 채워 넣은 토큰, <to be read again>은 한 번 읽었다가 되돌려 놓은 토큰, <read *>는 터미널 입력을 기다리고 있다는 표시입니다. 줄이 길어 터미널에 들어가지 않으면 앞부분이 ...으로 생략되므로, l.9 ...: $\frac{1}{2}$ and a stray \undefinedmacro 같은 표시를 보면 실제 줄의 시작은 더 왼쪽에 있다고 생각해야 합니다.
| 문맥 줄 | 의미 |
|---|---|
l.NN | 읽고 있던 입력 줄. 위아래로 잘린 자리가 멈춘 지점 |
\mac #1-> | \mac의 전개 중에 발생. 정의는 다른 곳에 있음 |
<inserted text> | TeX이 복구하려고 스스로 채워 넣은 토큰(흔히 $) |
<recently read> | 방금 읽어 들인 토큰. 대개 그 자체가 원인 |
<to be read again> | 읽었다가 되돌려 놓은 토큰. 다음에 다시 읽힘 |
<argument> | 인수 안에서 발생. 호출부가 아니라 인수를 볼 것 |
<read *> | 터미널 입력 대기. 비대화형 모드에서는 즉시 중단됨 |
l.NN이 정확히 한 줄 뒤를 가리키는 진짜 이유
\usepackage가 일으킨 오류는 대개 실제보다 한 줄 뒤에 보고됩니다. 원인은 \usepackage 끝에 붙일 수 있는 선택적 날짜 인수입니다. \usepackage[opt]{pkg}[2021/02/14]라는 형식이 허용되므로, TeX은 닫는 중괄호를 읽은 뒤 그다음에 [가 오는지 미리 살펴봐야 합니다. 이 미리 보기는 공백과 줄바꿈을 건너뛰기 때문에, 오류가 터지는 시점에는 이미 다음 줄을 읽어 들인 상태입니다. TeX Live 2024에서 측정한 결과, \usepackage[latin1]{inputenc}를 3행에 두면 옵션 충돌이 l.4로 보고되고, 같은 줄 끝에 [2021/02/14]를 명시하면 l.3으로 바뀝니다. 따라서 패키지 관련 오류가 빈 줄이나 \begin{document}를 가리킨다면 한 줄 위를 보십시오.
% \usepackage[latin1]{inputenc} sits on line 3 -- reported at line 4
./oc1.tex:4: LaTeX Error: Option clash for package inputenc.
l.4 \begin
{document}
% same source, but the optional date argument is written out
% -- the look-ahead ends on line 3, and so does the report
./ocA.tex:3: LaTeX Error: Option clash for package inputenc.
l.3 \usepackage[latin1]{inputenc}[2021/02/14]“TeX이 알아차린 곳”과 “실제 잘못된 곳” 사이의 이 간격은 닫지 않은 }에서도 생깁니다. 다만 이때는 한 줄이 아니라 수십 줄까지 벌어질 수 있고, TeX은 결국 문단 끝이나 \end{document}에서 손을 듭니다. 개별 증상——수식 모드 누락, 정의되지 않은 명령, 중괄호 부족——은 각각 전용 페이지가 다룹니다. 여기서 가져갈 것은 일반 규칙 하나뿐입니다. 보고된 줄이 멀쩡해 보일수록 원인은 더 위쪽에 있습니다.
-file-line-error: 편집기가 따라갈 수 있는 형식
-file-line-error를 붙이면 맨 앞의 !가 ./file.tex:3:으로 바뀌어, 파일 이름과 행 번호가 한 줄에 모이므로 편집기나 CI 로그 파서가 곧바로 그 자리로 이동할 수 있습니다. 기본 형식에는 실질적인 빈틈이 있습니다. l.3은 숫자만 알려 주고, 파일 이름은 한참 위에 찍힌 (./chapters/intro.tex 같은 여는 괄호에서 추측해야 합니다. \input으로 장을 나눈 문서에서는 바로 이 추측이 시간을 잡아먹습니다. -file-line-error는 그 추측을 없애 주며, l.NN 두 줄도 그대로 출력되므로 잃는 것이 없습니다.
$ pdflatex main.tex
(./chapters/intro.tex
! Undefined control sequence.
l.2 Here is \nosuchcmd
in a chapter.
$ pdflatex -file-line-error main.tex
(./chapters/intro.tex
./chapters/intro.tex:2: Undefined control sequence.
l.2 Here is \nosuchcmd
in a chapter.많은 환경에서는 이 형식이 이미 기본값입니다. latexmk는 내부에서 켜 주고, TeXworks나 VS Code의 LaTeX Workshop 같은 프런트엔드도 알아서 붙여 줍니다. 엔진을 직접 실행할 때는 -file-line-error를, 명시적으로 끄고 싶을 때는 -no-file-line-error를 넘기면 됩니다. 유용한 부수 효과가 하나 있습니다. 오류가 패키지에서 났을 때는 표시되는 경로가 그 패키지의 파일이 됩니다. /usr/local/texlive/…/foo.sty:120:이라고 나오면 그것은 작성한 문서의 문제가 아니라 foo가 항의하고 있다는 뜻입니다.
? 프롬프트의 응답: h, i, x, q, r, s와 Enter
? 프롬프트에서 가능한 응답은 아홉 가지이며, ?를 입력하면 TeX이 직접 그 목록을 인쇄합니다. 이것은 기본값인 errorstopmode의 동작으로, TeX이 말 그대로 어떻게 할지 묻고 있는 상태입니다. 실무에서 가장 많이 쓰는 것은 Enter(이 오류를 무시하고 계속), h(해당 메시지에 대한 TeX 자체 도움말 출력), x(즉시 중단, PDF 생성 없음) 세 가지입니다. 긴 문서에 오류가 더 있을 것 같으면 r이나 s를 눌러 끝까지 돌린 뒤 .log를 읽는 편이 빠릅니다.
? ?
Type <return> to proceed, S to scroll future error messages,
R to run without stopping, Q to run quietly,
I to insert something, E to edit your file,
1 or ... or 9 to ignore the next 1 to 9 tokens of input,
H for help, X to quit.
?| 응답 | TeX이 하는 일 |
|---|---|
Return | 이 오류를 없던 일로 하고 계속. 조판이 이어지고 PDF도 생성됨 |
h | 그 메시지 전용 도움말 문단을 출력. .log에는 이미 들어 있음 |
i | 그 자리에 문자열을 끼워 넣음. i\textbf로 이번 실행에만 오타를 고칠 수 있음 |
x | 즉시 중단. No pages of output.을 출력하고 PDF를 만들지 않음 |
q | OK, entering \batchmode을 표시하고 이후 아무 출력 없이 끝까지 실행 |
r | OK, entering \nonstopmode...을 표시하고 멈추지 않고 끝까지 실행 |
s | OK, entering \scrollmode...을 표시. 멈추지 않지만 터미널 입력은 계속 받음 |
e | 환경 변수 TEXEDIT에 지정한 편집기를 그 줄에서 엶 |
1 … 9 | 다음 1~9개의 토큰을 버리고 계속. 새 자른 자리에서 줄이 다시 표시됨 |
-interaction의 네 가지 모드와 각각의 쓸 자리
pdflatex --help가 나열하는 값은 batchmode, nonstopmode, scrollmode, errorstopmode 네 가지이고 기본값은 errorstopmode입니다. 스크립트가 실행을 몰 때는 -interaction=nonstopmode를, CI에서 터미널을 어지럽히고 싶지 않을 때는 -interaction=batchmode를 씁니다. 네 가지를 가르는 축은 “멈추는가”와 “터미널에 쓰는가” 둘뿐이며, 가장 자주 오해받는 것은 scrollmode와 nonstopmode의 차이입니다. 실측 결과, \typein으로 입력을 요구하는 문서는 scrollmode에서는 터미널 입력을 제대로 읽고, nonstopmode에서는 ! Emergency stop.으로 죽습니다. 경계선은 오류가 아니라 터미널 입력에 있습니다.
| 모드 | 멈추는가 · 터미널에 쓰는가 |
|---|---|
errorstopmode | 기본값. 모든 오류에서 ?로 멈추고 물음. 손으로 좇을 때 적합 |
scrollmode | 오류에서는 멈추지 않지만 터미널 입력은 읽음. 실행 전체를 훑을 때 |
nonstopmode | 터미널 입력을 전혀 하지 않음. 요구가 오면 ! Emergency stop.으로 종료 |
batchmode | nonstopmode에 더해 터미널 출력까지 막음. .log는 그대로 온전히 기록됨 |
batchmode가 “아무것도 출력하지 않는다”는 말은 정확히는 거의 아무것도 출력하지 않는다는 뜻입니다. TeX Live 2024에서 같은 오류 문서를 돌려 재 보면 터미널 출력은 nonstopmode에서 1212바이트, batchmode에서 144바이트입니다. 남는 것은 pdfTeX 배너와 entering extended mode뿐이며, 둘 다 대화 모드가 적용되기 전에 인쇄되기 때문입니다. 반면 .log는 두 경우 모두 4144바이트로 바이트 단위까지 동일하고, PDF도 똑같이 만들어집니다. 즉 batchmode는 정보를 버리는 것이 아니라 터미널로 흘리지 않을 뿐입니다. CI의 정석은 여기서 나옵니다. 배치 모드로 돌리고, 다음 절의 종료 코드로 성패를 판정하고, 자세한 내용은 .log를 회수해 읽습니다. 이 네 이름은 명령행 옵션인 동시에 TeX 프리미티브이므로, 파일 첫머리에 \nonstopmode라고 써도 같은 효과가 납니다.
-halt-on-error와 종료 코드
-halt-on-error는 첫 번째 오류에서 실행을 끊습니다. TeX Live 2024에서 확인한 결과, 첫 ! Undefined control sequence 바로 뒤에 ! ==> Fatal error occurred, no output PDF file produced!를 출력하고 종료하며 PDF는 남지 않습니다. 같은 문서를 -interaction=nonstopmode만으로 돌리면 네 건의 오류를 모두 보고하면서 PDF까지 만들어 내므로, 깨진 문서가 성공한 빌드처럼 보이면 곤란할 때 쓰는 스위치입니다. 종료 코드도 측정했습니다. 오류가 하나라도 있으면 1, 없으면 0입니다. 이는 모드와 무관해 nonstopmode나 batchmode나 같으며, 경고는 종료 코드를 바꾸지 않습니다. 따라서 Makefile이나 CI에서 pdflatex && …라고 쓰면 멈추는 것은 오류뿐입니다.
# interactive: stop at the first error and look at it
pdflatex -file-line-error -halt-on-error document.tex
# CI: quiet terminal, full log on disk, exit status decides
pdflatex -interaction=batchmode -halt-on-error -file-line-error document.tex
echo $? # 1 if any error occurred, 0 if none.log 보는 법: 터미널보다 많은 것이 들어 있습니다
.log에는 터미널에 나오지 않은 도움말 문단이 그대로 들어 있습니다. 그러니 메시지의 뜻을 모르겠을 때 상황을 재현해 h를 누를 필요 없이 로그를 열면 됩니다. 한 번의 실행을 측정한 결과 터미널에는 938바이트가 흘렀고 .log는 3199바이트였으며, 그 차이의 대부분이 이 도움말입니다. 효과가 가장 큰 것은 옵션 충돌입니다. 터미널에는 ! LaTeX Error: Option clash for package inputenc.만 나오지만, 로그에는 그 패키지가 처음 어떤 옵션으로 불러와졌고 지금은 무엇을 요구했는지가 구체적으로 적혀 있습니다. 이 네 줄이 있고 없고가 추측과 확인을 가릅니다.
% only the first line of this reaches the terminal
./oc1.tex:4: LaTeX Error: Option clash for package inputenc.
...
The package inputenc has already been loaded with options:
[utf8]
There has now been an attempt to load it with options
[latin1]
Adding the global options:
utf8,latin1
to your \documentclass declaration may fix this.로그 전체의 구조를 외워 두는 것도 이득입니다. 첫 줄에는 엔진 이름과 버전, 실행 날짜와 시각이, 다음 줄에는 기동 인수가 **document.tex 형태로 나오고, 그 뒤로는 전부 괄호의 중첩입니다. (로 파일을 열고 )로 닫으므로, 어떤 패키지가 어느 파일 안에서 불려 왔는지는 괄호의 중첩이 답해 줍니다. [1], [2]는 출력된 페이지 표시이고, 끝부분은 Here is how much of TeX's memory you used:에 이어지는 메모리 집계와 Output written on document.pdf (1 page, 12817 bytes).입니다. 이 전부를 상대하고 싶지 않다면 TeX Live에 함께 오는 texfot을 거치면 오류와 경고, 마지막 요약 줄만 남습니다.
$ texfot pdflatex -interaction=nonstopmode two.tex
texfot: invoking: pdflatex -interaction=nonstopmode two.tex
This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024)
! Undefined control sequence.
l.3 First \foo
! Missing $ inserted.
<inserted text>
l.4 Second \bar
Output written on two.pdf (1 page, 23091 bytes).\listfiles와 *File List*: 실제로 불려 온 것을 세기
프리앰블 어디에든 \listfiles 한 줄을 넣으면 .log 끝에 *File List* 표가 붙어, 불려 온 모든 파일이 날짜・버전・한 줄 설명과 함께 나열됩니다. TeX Live 2024에서 세어 보면 맨 article은 3개 파일(article.cls, size10.clo, l3backend-pdftex.def)입니다. 여기에 hyperref 한 줄만 더하면 33개가 됩니다. 즉 hyperref 혼자서 30개를 끌고 옵니다. tikz는 34개입니다. 불러온 기억이 없는 패키지가 충돌에 얽혀 있을 때 가장 먼저 쓸 수단이 이것입니다. 질문을 올리거나 버그를 보고할 때도 이 표를 붙이면 두 환경의 차이가 한눈에 드러납니다.
% \listfiles goes anywhere in the preamble; this lands at the end of the .log
*File List*
article.cls 2023/05/17 v1.4n Standard LaTeX document class
size10.clo 2023/05/17 v1.4n Standard LaTeX file (size option)
amsmath.sty 2023/05/13 v2.17o AMS math features
hyperref.sty 2024-01-20 v7.01h Hypertext links for LaTeX
iftex.sty 2022/02/03 v1.0f TeX engine tests
***********더 세밀하게 보고 싶으면 -recorder를 붙입니다. 실행 중 열린 모든 파일이 .fls 파일에 INPUT 줄로 기록되며, 패키지라고는 tikz 하나뿐인 문서도 140줄이 나옵니다. \listfiles가 “어떤 패키지가 불려 왔는가”에 답한다면, .fls는 글꼴 .tfm이나 설정 파일까지 포함해 “어떤 파일을 건드렸는가”에 답합니다. 패키지 충돌을 좇을 때는 앞의 것이, kpathsea가 실제로 어디를 뒤졌는지 좇을 때는 뒤의 것이 알맞습니다.
\show / \showthe / \typeout: TeX의 머릿속 들여다보기
\show\foo는 \foo의 정의를, \showthe\textwidth는 길이나 카운터의 값을 인쇄합니다. 출력은 .log에 > \LaTeX=macro:나 > 345.0pt. 형태로 남고, 줄 앞의 >가 그 표시입니다(참고로 345.0pt는 article의 기본 \textwidth입니다). 어떤 명령이 지금 어떻게 정의돼 있는지 헷갈릴 때는 추측보다 \show가 빠르며, 재정의한 쪽이 클래스인지 패키지인지도 대개 이것으로 가려집니다. 직접 메시지를 내보내려면 \typeout{…}과 \message{…}가 있는데, 측정해 보면 \typeout은 자기 줄에 쓰고 \message는 현재 줄에 이어 붙입니다. printf식 디버깅에는 앞의 것이 읽기 좋고, 페이지 번호 옆에 표시를 남기고 싶을 때는 뒤의 것이 편합니다.
\show\LaTeX % > \LaTeX=macro: ... (definition follows)
\showthe\textwidth % > 345.0pt. (article default)
\typeout{reached the theorem} % own line in log and terminal
\message{mark} % appended to the current line
\tracingall % dump every step to the log -- extremely verbose최후의 수단은 \tracingall로, TeX이 하는 모든 단계——매크로 전개, 모드 전환, 줄바꿈 시도——를 로그에 씁니다. 몇 쪽짜리 문서에서도 수십 MB에 이를 수 있으므로, 원칙적으로 문제 직전에 켜고 직후에 \tracingnone으로 되돌리거나, 출력을 읽기 좋게 다듬어 주는 trace 패키지와 함께 쓰십시오. \tracingall은 “어떤 매크로가 잘못했는가”가 아니라 “어떤 순서로 일어났는가”에 답하는 도구입니다. 순서만 잡히면 나머지는 대개 \show 한 번으로 끝납니다.
이분 탐색으로 범인 좁히기: \end{document}를 위로 옮기기
메시지만으로 원인을 알 수 없을 때는 문서를 반으로 자르는 것이 가장 빠릅니다. 본문 중간에 \end{document}를 하나 더 써 넣으면 그 뒤는 통째로 무시됩니다. TeX Live 2024에서 확인한 대로, \end{document} 뒤에 무엇이 쓰여 있든——깨진 명령이라도——읽히지 않습니다. 그러니 원래의 것을 지울 필요조차 없고, 추가한 한 줄을 위아래로 옮기며 양쪽에서 좁혀 가면 됩니다. 열 번만 옮기면 1000행짜리 문서가 한 줄로 좁혀집니다. 프리앰블이 의심스러우면 \usepackage 줄을 절반씩 %로 주석 처리하고, \include로 장을 나눴다면 \includeonly{chapter3}를 쓰십시오.
\begin{document}
\input{chapters/intro}
\input{chapters/method}
\end{document} % <- added: bisect here, everything below is ignored
\input{chapters/results}
\input{chapters/discussion}
\end{document}절반까지 좁혔다면 여전히 실패하는 가장 작은 형태까지 계속 깎아 내십시오. \usepackage를 하나씩 빼고, 본문을 문단 단위로 버리고, 그림은 graphicx에 함께 오는 example-image로, 긴 문단은 lipsum으로 바꾸다 보면 마지막에는 열몇 줄이 남습니다. 이 정도 크기면 원인은 대개 자명해지고, 그래도 모르겠다면 그 열몇 줄이 곧 질문에 붙일 최소 예제가 됩니다. 깎는 작업 자체가 진단입니다. 잘 묻는 법과 어디에 물을지는 커뮤니티 페이지가 다룹니다.