Why can I say \input something but not \centerline something?

\centerline is a macro, defined in plain.tex with \def\centerline#1{\line{\hss#1\hss}}, so it takes an argument. As usual, an argument will be a single token (S, for example, is a single token), unless this token is a {, in which case a {...}-balanced list of tokens will be taken as the argument.

In Plain TeX \input is a primitive, so its rules are different. The primitive will look for character tokens and will stop at the first space token[1]. In LaTeX, \input is a macro, but if the next token is not a {, then it behaves as the primitive input. If the next token is a {, then \input behaves as a macro and grabs the file name as argument.

You can kind-of emulate that with \centerline by making it grab the rest of the line of input:

\def\centerline{%
  \begingroup
  \catcode`\^^M=13
  \centerlineaux}
\begingroup
\catcode`\^^M=13
\gdef\centerlineaux#1^^M{\endgroup\line{\hss#1\hss}}
\endgroup

\centerline This entire line is centered.
  But this is not.

\bye

1 There are different implementations of \input: Knuth's \input will scan character tokens until a non-character token or a space is found. If that token was a space, it is discarded. The characters found are taken as the file name.
The Web2C implementation (TeXLive and MiKTeX use that) of \input allows spaces in file names as long as the space occurrs between two ".
Finally, in all TeX engines from TeXLive 2020 onwards (before that only in LuaTeX), if the next token after the primitive \input is a {, the file name is grabbed between braces (\input{story} will input story.tex). Since this is what Knuth calls a "filesystem-dependent change", this applies to tex as well.


\input is actually a primitive, and they are defined as part of the TeX core, not as a separate macro that is built on other macros. This is probably best explained if you look at the definition of \input and friends (within latex.ltx):

\ifx\@@input\@undefined\let\@@input\input\fi
% <some other definitions>
\def\input{\@ifnextchar\bgroup\@iinput\@@input}
% <some other definitions>
\def\@iinput#1{%
  \InputIfFileExists{#1}{}%
  {\filename@parse\@curr@file
   \edef\reserved@a{\noexpand\@missingfileerror
     {\filename@area\filename@base}%
     {\ifx\filename@ext\relax tex\else\filename@ext\fi}}%
   \reserved@a}}

Note that the sequence of commands stores \input - the TeX primitive definition - inside \@@input at the start of loading the LaTeX kernel. Then it (re)defines \input to be conditional via \@ifnextchar. This conditional checks if you supplied an opening curly brace { (or \bgroup). If so, it calls \@iinput. This would be the case when you used

\input{story}

The definition of \@iinput implies it takes a single, mandatory argument, like any other macro you use. This makes sense from your original use case. However, why does \input story work the same way? For that, we follow the false branch in the \@ifnextchar conditional. That is, \@@input.

Since \@@input is an exact copy of the \input primitive which, reads a filename as the string of non-empty characters following \input. Reference to this process is given in scan_file_name within tex.web.

Tags:

Tex Core