> For the complete documentation index, see [llms.txt](https://ayakaleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ayakaleaf-pro.ayaka.space/latex/zh-cn/shen-du-wen-zhang/05-an-introduction-to-endlinechar-how-tex-reads-lines-from-text-files.md).

# \endlinechar 简介：TeX 如何从文本文件中读取行

作为人类，我们更喜欢按行来查看和编辑文本文件。当我们觉得某一行文本已经足够长时，就会在（文本编辑器中）按下“回车”来表示这一行结束。在幕后，你的文本编辑器会把这理解为一条指令，即添加一个 [换行字符](https://en.wikipedia.org/wiki/Newline) 到你决定断行的位置。

然而，事情并没有这么简单：麻烦在于，不同的操作系统对什么构成换行字符有不同的定义。更糟糕的是，Windows 会根据文件是在所谓的 [*二进制模式* 或 *文本模式*](https://docs.microsoft.com/en-us/cpp/c-runtime-library/text-and-binary-mode-file-i-o?view=vs-2017)下打开而以不同方式处理换行字符。结果就是，取决于宿主操作系统，文本文件中的行可能会以不同组合的字符结束，这些字符称为 *回车符* （ASCII/Unicode 字符 13）以及 *换行符* （ASCII/Unicode 字符 10）：分别表示为 `\r` 和 `\n` 。

显然，为了保持系统无关性，TeX 需要一种方法来处理它需要读取和处理的文本文件中用于结束行的不同字符所带来的种种差异。

### TeX 的输入缓冲区

你也许会感到惊讶，也许不会：TeX 引擎（包括 LuaTeX 和 XeTeX）是按行读取输入文件的：它们不会把整个文本文件读入内存。尽管 TeX 引擎处理的大多数文本文件与现代设备可用内存相比都微不足道，但文件中的每一行都会被单独读取并存储到一个小的内部缓冲区中。当然，TeX 读取并存储一行的过程还有一些额外的变化。

## “我自有我的方式”——TeX 的 \endlinechar 命令

当 TeX 从输入文件中再读取一行文本时，它会执行两项“整理工作”：

* 它会移除在行尾找到的任何终止换行字符（\r 或 \n）——也就是说，它会剥除文本文件最初保存到磁盘时添加的所有行尾标记；
* 它还会移除行尾发现的所有尾随空格字符。

这两个过程发生在 TeX 真正开始扫描该行中所包含的字符之前：可以把它们看作是为下一阶段处理（扫描）做准备的一种“整理”。因此，在这一行读取过程的初始阶段，TeX 已经剥离了所有平台相关的行尾（以及任何尾随空白）：那么 TeX 将如何知道（检测）这一行的结束位置呢？TeX 还有一个“妙招”：即 `\endlinechar` 该命令。

为避免平台相关的换行字符问题，TeX 引入了 `\endlinechar`一个可由用户定义的参数，TeX 用它将自己专属的行尾字符插入到它刚从文件中读取的一行文本的最末尾。再次注意，这一切发生在 TeX 真正开始扫描字符之前——这是 TeX 在准备开始读取（扫描）该行中实际包含的字符之前所做的最后一步“整理”工作。

TeX 会使用存储在 `\endlinechar` 中的值来添加它自己的行尾终止符，且仅当 `\endlinechar` 已被适当地定义——在 Knuth 的 TeX 中，这意味着它必须具有大于 -1 且小于 256 的值。通常， `\endlinechar` 会被赋值为 13：即回车符——通常表示为 `\r` ，在编程文献中。

但如果你写下 `\endlinechar=-1` 在你的输入中的某处，那么 *下一次* TeX 从文件中读取一行文本时，它将 *不* 不会在行尾添加任何额外的终止符。因此，在你将其重置 `\endlinechar` 为合适的值——通常为 13（`\r`):

```latex
\endlinechar=13
```

TeX 的 16 个类别码之一（值为 5）专门用于标识“行尾”字符，而这通常就是那个 `\endlinechar` 被插入的字符——而它仅在 `\endlinechar` 被设为合适的值时才会被插入。

## 行尾处理摘要

虽然这些细节相当底层，但对于任何想探索编写处理文本行读取的宏的人来说，它们都很有意思。

1. 当 TeX 从你的文件中读取一行时，它会先剥离所有行尾字符（`\r` 和 `\n`）——这些字符是文本编辑器在文件保存时添加的。除此之外：

* TeX 还会剥离行尾的任何尾随空格字符；
* TeX *不会* 删除尾随的制表符（ASCII 字符代码 9）。

**附注**：LuaTeX 的一个源代码文件——其中包含执行这种空格剥离的代码——有以下说明：

> （引自文件 `luatex.c`）：“David Fuchs 提到，进行这种 \[空格] 剥离是为了确保 TeX 文档的可移植性，因为当时某些系统（例如 IBM VM/CMS 和 OS/360）在固定记录的“行”上会用空格进行填充。”

3. 在步骤 (1) 之后，TeX 会添加（插入）一个附加字符，其值存储在 \endlinechar 中（前提是它被适当地定义：大于 -1 且小于 256）
4. \endlinechar 通常被设为 13（`\r`），这意味着在步骤 (2) 中添加的字符通常是字符 13（`\r`）——当然，你也可以将 \endlinechar 设为其他值，以通过宏编程实现特殊效果。
5. 当其输入扫描例程检测到字符 `\r` （字符代码 13）出现在其内部缓冲区末尾时，TeX 将照常检查其类别码，以决定如何处理它。
6. 字符 13 *通常* 的类别码值为 5（“行尾”），当然，除非它的类别码已被更改——某些宏会将行尾字符设为活动字符，以便进行更复杂的处理。
7. 根据 TeX 的内部状态（实际上就是它正在做什么），TeX 可以把行尾字符（通常 `\r`，类别码 5）转换为空格字符——这就是行尾字符变成空格的方式。
8. 还要注意，TeX 使用类别码为 5 的字符来检测何时它读取了一个空行，并需要生成一个 `\par` 记号。

下图直观地总结了步骤 (1) 和 (2)：剥离换行字符和尾随空格字符，并插入 `\endlinechar` ，以便为扫描输入做好准备。

![TeX 如何使用 \endlinechar](/files/8a004c446b4a9a16c63ac981dea60022a1b38a7f)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ayakaleaf-pro.ayaka.space/latex/zh-cn/shen-du-wen-zhang/05-an-introduction-to-endlinechar-how-tex-reads-lines-from-text-files.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
