Missing $ inserted

! Missing $ inserted는 LaTeX에서 가장 자주 보는 오류이며, 그 아래에 이어지는 도움말은 Proceed, with fingers crossed(손가락을 겹치고 행운을 빌며 계속합니다)라는 문장으로 끝납니다. 도널드 커누스가 1980년대에 TeX 자체에 써 넣은 문구가 지금도 매일 누군가의 화면에 나오는 셈입니다. 뜻은 하나뿐입니다. 수식 안에서만 통하는 것이 본문에 나타났다는 것입니다. 범인은 대개 글자 그대로 쓰고 싶었던 밑줄 _, 위첨자 ^, 본문에 섞여 든 \alpha, 또는 한참 앞에서 닫지 않은 $입니다. 이 페이지에서는 메시지를 읽는 법, 보고된 줄 번호가 실제 실수보다 뒤인 이유, 그리고 답이 달라지는 세 곳——본문, \verb, \url——에서 무엇을 써야 하는지를 다룹니다.

! Missing $ inserted는 무엇에 화를 내는가

본문을 조판하는 도중에 수식 모드 전용 토큰이 등장했다는 뜻, 그뿐입니다. TeX는 “수식을 여는 것을 잊었겠지”라고 추측해 $를 슬쩍 넣고 계속 조판합니다. 그래서 해결도 한 문장으로 끝납니다. 수식으로 내고 싶으면 $x_1$처럼 감싸고, 글자 자체를 내고 싶으면 \_처럼 이스케이프합니다. 한 단계 아래에는 카테고리 코드(catcode) 라는 구조가 있습니다. TeX는 입력 문자마다 번호를 부여하는데 _는 8(아래첨자), ^는 7(위첨자), $는 3(수식 전환)입니다. 그리고 catcode 7과 8은 수식 모드 안에서만 합법입니다. \alpha\frac이 오류를 내는 것도 같은 이치로, 내부에서 수식 클래스의 문자를 요구하기 때문입니다.

latex
% "$" is not sacred: it is just the character whose catcode is 3.
% Give catcode 3 to "!" and it opens and closes math exactly like $.
\catcode`\!=3
Then !x_1! typesets as math.   % compiles with zero errors

위 코드는 농담처럼 보이지만 TeX Live 2024에서 오류 하나 없이 통과합니다. $는 신성한 기호가 아니라 단지 catcode가 3인 문자일 뿐입니다. 이것이 이해되면 이 오류는 더 이상 신비롭지 않습니다. 문자의 역할을 착각한 것뿐이니까요. 그리고 본문에서 말썽을 부리는 특수 문자가 _^만은 아니라는 결론도 따라옵니다. 다만 문자가 다르면 오류도 다릅니다. 아래 표로 증상을 짚어 보세요. 흔한 오해가 하나 있는데, 본문에 &를 써도 ! Missing $ inserted는 나오지 않습니다.

문자catcode본문에 쓰면 일어나는 일
_8! Missing $ inserted. 글자 그대로의 밑줄은 \_
^7! Missing $ inserted. 글자 그대로는 \textasciicircum
$3수식을 열고 닫음. 하나만 있으면 문단 끝까지 수식이 이어짐
&4다른 오류: ! Misplaced alignment tab character &. 글자 그대로는 \&
%14오류가 전혀 안 남. 줄의 나머지를 삼켜 닫는 $까지 없앰
#6! You can’t use ‘macro parameter character #’ in horizontal mode
~13오류 없음. 줄바꿈 금지 공백이 됨(글자 그대로는 \textasciitilde)

<inserted text>l.3 읽는 법

<inserted text> 아래에 보이는 $TeX가 스스로 덧붙인 것이며 여러분이 쓴 문자가 아닙니다. 반면 l.3 줄은 범행 현장 그 자체입니다. TeX는 문제의 줄을 두 토막으로 접어 보여 주는데, 접힌 자리가 곧 “여기까지 읽었다”는 지점입니다. 아래는 본문이 The value x_1 is here. 뿐인 네 줄짜리 파일을 pdfLaTeX(TeX Live 2024)로 돌린 실제 출력입니다. 접힌 자리가 x_ 바로 뒤라는 점에 주목하세요. 밑줄을 읽은 순간 TeX가 멈춘 것입니다. 이 두 토막 표시는 긴 줄 안에서 어디가 막혔는지 짚어 주므로, TeX 오류 표시에서 가장 쓸모 있는 부분입니다.

log
! Missing $ inserted.
<inserted text>
                $
l.3 The value x_
                1 is here.
I've inserted a begin-math/end-math symbol since I think
you left one out. Proceed, with fingers crossed.

같은 메시지는 plain TeX에서도 한 글자도 다르지 않게 나옵니다. 즉 이것은 LaTeX의 메시지가 아니라 TeX 엔진 자체의 메시지이며, 파일에 \usepackage가 한 줄도 없어도 나타납니다. 원인을 찾을 때 패키지보다 먼저 자기가 친 문자를 의심해야 하는 이유가 여기에 있습니다. 참고로 도움말의 마지막 문장 Proceed, with fingers crossed는 1990년에 TeX이 동결된 이래 사실상 그대로입니다.

보고된 줄 번호가 실수한 줄보다 뒤인 이유

TeX가 보고하는 것은 “알아챈 줄”이지 “틀린 줄”이 아닙니다. _처럼 읽는 순간 불법인 것이라면 둘이 일치합니다. 그러나 $를 닫지 않아 수식이 열린 채 남는 종류의 실수라면, TeX는 문단이 끝날 때——곧 다음 빈 줄에서——비로소 알아챕니다. 수식은 문단을 넘을 수 없으므로, 빈 줄에 닿은 순간이 “아직 수식이 열려 있다”고 판정할 수 있는 첫 시점입니다. 아래 파일에서 실수는 3행에 있지만 오류는 4행, 즉 빈 줄에서 보고됩니다.

document.tex
\documentclass{article}
\begin{document}
The rate is $x% of the total$ per year.

Next paragraph.
\end{document}
log
! Missing $ inserted.
<inserted text>
                $
l.4

I've inserted a begin-math/end-math symbol since I think
you left one out. Proceed, with fingers crossed.

함정은 3행의 %입니다. 이스케이프하지 않은 퍼센트 기호가 그 줄의 나머지——닫는 $까지 포함해서——를 통째로 주석 처리해 버려, 수식이 열린 채 다음 줄로 넘어갑니다. l.4 오른쪽에 아무것도 없는 이유는 4행이 바로 그 빈 줄이기 때문입니다. 오류 줄이 빈 줄이면 “그보다 앞 어딘가에서 수식이 열린 채로 있다”고 읽으세요. 실무에서 가장 빠른 방법은 보고된 줄 바로 앞 문단으로 가서 $의 개수를 세는 것입니다. 편집기는 중괄호는 짝을 맞춰 주지만 $는 맞춰 주지 않으니 이 단계는 직접 해야 합니다. 같은 이치로 50\%라고 써야 할 자리에 50%라고 쓴 줄은 몇 줄 뒤에 정체불명의 오류를 만듭니다.

글자 그대로의 밑줄 내기: \_, \textunderscore, \verb 안의 _

data_set.csv 같은 파일명을 내고 싶을 뿐이라면 답은 data\_set.csv입니다. 다만 \_\textunderscore의 관계는 알아 둘 값어치가 있습니다. 커널 파일 latex.ltx를 보면 \_는 “수식 모드면 \nfss@text{\textunderscore}, 아니면 \textunderscore”로 정의되어 있어 실체가 같습니다. 즉 본문에서 둘은 완전히 같고, \_는 수식 모드에서도 쓸 수 있는 짧은 형태일 뿐입니다. 더 중요한 것은 그다음입니다. 기본 OT1 인코딩에서 밑줄은 문자로 조판되는 것이 아니라 괘선으로 그려집니다. 커널의 정의는 \leavevmode \kern.06em\vbox{\hrule\@width.3em}, 곧 폭 0.3em의 가로선입니다. 그 결과 PDF에서 밑줄을 복사할 수 없고 PDF 내 검색에도 걸리지 않습니다. \usepackage[T1]{fontenc}를 넣으면 t1enc.def\DeclareTextSymbol{\textunderscore}{T1}{95}가 작동해 진짜 글리프가 되고 복사와 검색이 됩니다. 같은 문서를 pdftotext에 넣으면 OT1 판은 data set, T1 판은 data_set이 나옵니다.

쓰는 법결과쓸 자리
\__표준 표기. 본문에서도 수식에서도 쓸 수 있음
\textunderscore_\_의 실체. 본문에서는 차이 없음
\verb|a_b|a_b이스케이프 불필요. 안에 \_를 쓰면 역슬래시까지 나옴
underscore_패키지. 불러오면 본문의 맨 _가 안전해짐
\textasciicircum^글자 그대로의 ^. 위첨자가 아님
latex
% math, when you mean math
The value $x_1$ and $\alpha$ are positive, and $x^2 \ge 0$.

% characters, when you mean characters
Open data\_set.csv, then press \textasciicircum{}C to stop.

% verbatim, when the string is code
Run \verb|make data_set^2| in the shell.

\urlsiunitx 안에서는 답이 뒤집힌다

\url{...} 안에서는 _를 이스케이프하면 안 됩니다. \url{https://example.com/a_b}는 그대로 정상 통과하고, 반대로 \url{a\_b}라고 쓰면 화면에 역슬래시까지 붙은 a\_b가 나옵니다. url 패키지가 인자를 읽기 전에 카테고리 코드를 갈아 끼워 _를 비롯한 특수 문자를 “평범한 문자”로 되돌리기 때문입니다. siunitx도 같은 취지로, \num\qty는 내부에서 스스로 수식 모드를 열기 때문에 \qty{2.5}{\micro\metre}를 본문에 그대로 쓸 수 있습니다. 즉 어느 명령의 인자 안에 있느냐에 따라 써야 할 것이 달라집니다. 판별법은 간단합니다. 그 명령이 내용을 verbatim으로 다루면 맨 문자를, 아니면 이스케이프한 형태를 씁니다. \verb, \url, listings\lstinline이 전자이고, \section, \caption, \footnote가 후자입니다.

latex
\usepackage{url}       % or hyperref, which loads url
\usepackage{siunitx}
...
\url{https://example.com/a_b}    % right: bare underscore
\url{https://example.com/a\_b}   % wrong: prints the backslash
\qty{2.5}{\micro\metre}          % siunitx opens math for you

\[ … \] 안의 빈 줄과 ! Display math should end with $$

별행 수식 안에 빈 줄을 하나 넣으면 오류가 세 개 나옵니다. 식을 보기 좋게 나누려고 줄을 띄웠을 뿐인데 문서가 깨집니다. \[ E = mc^2F = ma 사이에 빈 줄을 두면 TeX Live 2024는 ! Missing $ inserted, ! Display math should end with $$, ! LaTeX Error: Bad math environment delimiter를 차례로 뱉습니다. 셋 다 “수식은 문단 경계를 넘을 수 없다”는 한 가지 규칙에서 나옵니다. 식을 나누려면 빈 줄 대신 align 환경이나 \\를 쓰고, 중간에 문장을 넣고 싶으면 amsmath\intertext를 쓰세요. 참고로 ! Display math should end with $$$$ … $처럼 여는 달러와 닫는 달러의 개수가 어긋날 때도 나옵니다. LaTeX에서는 $$가 아니라 \[ … \]를 쓰는 것이 정석인데, $$\abovedisplayskip 같은 별행 간격 설정을 무시하기 때문입니다.

log
! Missing $ inserted.
<inserted text>
                $
l.5

! Display math should end with $$.
<to be read again>
                   \tex_par:D
l.5

! LaTeX Error: Bad math environment delimiter.
l.7 \]

실수 하나가 오류 다섯 개가 될 때

오류가 쏟아지면 고칠 것은 첫 번째 하나뿐입니다. 나머지는 거의 언제나 TeX가 끼워 넣은 $가 일으킨 2차 피해입니다. 본문이 \frac{1}{2} 하나뿐인 파일을 돌리면 세 개가 사슬로 이어집니다. ! Missing $ inserted(\frac을 읽은 지점), ! Extra }, or forgotten $.(\frac 정의 안 \over의 닫는 중괄호), 그리고 \end{document} 지점에서 다시 ! Missing $ inserted. 첫 오류 뒤로도 TeX는 수식 모드에 들어간 채 계속 달리므로, 이후의 본문이 전부 수식으로 해석되어 단어 중간에 이상한 공백이 생기고 문장 부호 간격이 무너지며 추가 오류가 쏟아집니다. 그래서 절차는 정해져 있습니다. 첫 오류를 고치고 다시 컴파일하십시오. 그전에 두 번째 이후 메시지를 읽는 것은 시간 낭비입니다.

  • 메시지에 나온 l.N으로 가서 그 줄을 접힌 자리까지 읽습니다. 접힌 자리 바로 뒤의 문자가 범인입니다.
  • 접힌 자리가 빈 줄이면 그 앞 문단에서 $의 개수를 셉니다. 홀수면 하나가 닫히지 않은 것입니다.
  • 같은 줄을 끝까지 훑어, 주석으로 의도하지 않은 %가 닫는 $를 삼키지 않았는지 확인합니다.
  • 수식으로 낼 것이면 $ … $, 문자로 낼 것이면 \_, \%, \&, \#, \textasciicircum{}를 씁니다.
  • 코드나 식별자를 그대로 보이려면 \verblistings를 씁니다. 문자마다 이스케이프를 고민할 필요가 없어집니다.
  • 고친 뒤에는 반드시 다시 컴파일하고 나서 다음 오류를 읽습니다.