\immediate\write 用纯文本

\immediate\write 用纯文本

我已经阅读问题在 TeX.SE 中,但我不希望用户^^J手动添加。也就是说,我希望 writer 本身就输出内容。

\documentclass{article}
\begin{document}
\newwrite\file
\immediate\openout\file=tmp.txt
\immediate\write\file{
To be or not to be,
that is % the question
}
\closeout\file
\end{document}

它应该输出

To be or not to be,
that is % the question

这是我的源代码,一开始我使用 Python 从文件中提取内容.tex,然后我用更简单的方式重构它,原本使用 LaTeX 输出代码,这就是我遇到这个问题的原因。

抱歉我的表达能力不佳:P 非常感谢!

答案1

LaTeX 内核提供了filecontents写入外部文件的环境,无需担心 catcode 等。在较旧的 LaTeX 版本中(2019-10-01 之前;请参阅这里) 该filecontents软件包对此环境做了最小的更改,使其可以在文档的任何位置使用(LaTeX 版本只能在序言中使用,不允许覆盖)。在较新的版本中,此功能也包含在 LaTeX 内核中。

生产

To be or not to be,
that is % the question

你用:

\documentclass{article}
% \usepackage{filecontents} For older LaTeX releases
\begin{document}
\begin{filecontents*}[overwrite]{tmp.txt}
To be or not to be,
that is % the question
\end{filecontents*}
\end{document}

带星号的版本 ( filecontents*) 省略了在环境标准版本中打印的标题:

%% LaTeX2e file `tmp.txt'
%% generated by the `filecontents' environment
%% from source `test' on 2020/01/12.
%%
To be or not to be,
that is % the question

关于我的(确实很懒惰的)答案的附录:

如果你坚持要重新发明轮子(我必须承认,这更有趣),那么你可以创建一个命令来\catcode为你处理这个任务。这里我提供了一个命令的实现\verbwrite,它可以为你完成这项工作。

该命令的语法与 LaTeX 的 有点相似\verb:您可以使用 as\verbwrite\file{<stuff>}\verbwrite\file|<stuff>|。对于后一种语法,{可以使用 以外的任何字符来分隔内容。显然,此字符不能出现在 中。第二种语法的优点是,您在命令内容中平衡和<stuff>没有任何限制。{}

\documentclass{article}

\makeatletter
\long\def\@ifnextchar@other@space#1#2#3{%
  \let\reserved@d=#1%
  \def\reserved@a{#2}%
  \def\reserved@b{#3}%
  \futurelet\@let@token\@ifnch@other}
\def\@ifnch@other{%
  \ifx\@let@token\other@sptoken
    \let\reserved@c\@xifnch@other
  \else
    \ifx\@let@token\reserved@d
      \let\reserved@c\reserved@a
    \else
      \let\reserved@c\reserved@b
    \fi
  \fi
  \reserved@c}
{\catcode`\ =12
{\global\let\other@sptoken= }%
\gdef\@xifnch@other {\futurelet\@let@token\@ifnch@other}}%
\def\verbwrite{%
  \kernel@ifnextchar*%
    {\let\@ifnextchar\@ifnextchar@other@space\expandafter\verbwrite@grab\@gobble}%
    {\let\@ifnextchar\kernel@ifnextchar\verbwrite@grab}}
\def\verbwrite@grab#1{%
  \begingroup
    \catcode`\^^M=13
    \newlinechar`\^^M
    \let\do\@makeother \dospecials
    \catcode`\{=1
    \@ifnextchar\bgroup
      {\catcode`\}= 2\relax\verbwrite@brace#1}%
      {\catcode`\{=12\relax\verbwrite@other#1}}
\def\verbwrite@brace#1#2{%
    \immediate\write#1{\unexpanded{#2}}%
  \endgroup}
\def\verbwrite@other#1#2{%
  \def\verbwrite@delim##1##2#2{%
    \verbwrite@brace##1{##2}}%
  \verbwrite@delim#1}
\makeatother

\begin{document}
\newwrite\file
\immediate\openout\file=tmp.txt
\verbwrite\file {1-To be or not to be,
that is % the question}
\verbwrite*\file {2-To be or not to be,
that is % the question}
\verbwrite\file|3-To be or not to be,
that is } the {question|
\verbwrite\file$4-Être ou ne pas être,
вот в чем вопрос$
\verbwrite\file}5-Être ou ne pas être,
вот в чем вопрос}
\closeout\file
\end{document}

请注意,我花了 68 分钟编写此命令,因此它肯定不能称为强大。请谨慎操作 :)

修复1:防止使用 ε-TeX 扩展文本\unexpanded(感谢 jfbu:)

修复2:防止过早标记分隔符(再次感谢 jfbu:)

特征 1:添加了带星号的版本,该版本忽略了逐字内容分隔符前的空格。

修复 3:实际上允许}作为“其他”分隔符(\verbwrite\file}stuff})(感谢 Ulrich Diez:)

修复 4:修复功能 1 中的缺陷。一旦使用,参数的效果*将会保留以供进一步调用。\verbwrite

答案2


下面的解释中,我在想要表明所写内容对“纯”TeX 有效,因此对 LaTeX 也有效的地方写了 (La)TeX。我这样做是为了让那些不知道 LaTeX 基本上是 TeX 加上一组宏的人知道,这些宏构成了 LaTeX 格式,并且在执行 latex.exe/latex-binary 时会自动加载。


我建议使用filecontents*-environment。

请注意,还有一个LaTeX 2ε 软件包文件内容filecontents*它确实消除了LaTeX 2ε 内核的环境所带来的一些限制。


如果你想重新发明轮子,你可以编写一个宏来

  • 切换到 verbatim-catcode-régime,
  • 将 endlinechar (通常为 /ASCII-Return) 的 catcode 切换^^M为 12,以便将 ASCII-return 视为数字和标点符号,
  • 在该 catcode-régime 下读取并标记包含要写入文件的文本的参数
  • 修剪文本的前导和尾随行符
  • 将文本写入文件,同时\endlinechar也将其作为\newlinechar

在 (La)TeX 中,处理输入有几个阶段。

(La)TeX 确实会逐行读取 TeX 输入,例如 .tex 输入文件。

在预处理阶段,组成行的单个字符将被转换为 (La)TeX 的内部字符编码。(对于老式 (La)TeX 引擎,内部字符编码是 ASCII。对于基于 XeTeX 或 LuaTeX 的引擎,内部字符编码是 utf-8,其中 ASCII 是其子集。)然后,将删除行右端出现的所有空格字符(在 ASCII 和 utf-8 中代码点编号都是 32,即,在所有涉及 (La)TeX 引擎内部字符编码的编码中) 。然后在行的右端插入一个字符,其在 (La)TeX 内部字符编码(即 ASCII 或 utf-8)中的代码点编号对应于整数参数的数量\endlinechar。通常,整数参数的值为\endlinechar13,而 ASCII 和 utf-8 中的代码点号 13(即,在所有涉及 (La)TeX 引擎内部字符编码的编码中)表示⟨返回⟩-字符。这意味着:通常是⟨返回⟩-字符插入到行的右端。

完成后,标记化阶段开始:在此阶段,(La)TeX 将构成行的字符作为将标记放入标记流的指令。在此阶段,事情开始涉及所谓的标记,例如控制序列标记(有两种形式:控制字标记和控制符号标记)和字符标记。字符标记由表示 (La)TeX 内部字符编码中的代码点编号的字符代码和类别代码组成。类别代码使字符对 (La)TeX 引擎具有特殊含义。例如,反斜杠字符的类别代码通常为 0(转义)。在标记化时类别代码为 0 的字符会导致 (La)TeX 收集控制序列标记的名称,然后将该控制序列标记放入标记流中。例如,打开花括号的类别代码通常为 1(开始分组),而关闭花括号的类别代码通常为 2(结束分组),而类别代码 1(开始分组)的字符标记用于引入组(即,由几个标记组成的宏参数或用于宏定义或⟨平衡文本⟩)之类的内容\scantokens以及类别代码 2(结束分组)的字符标记将用于表示不再属于相关组的内容。有关类别代码的更多信息,请访问https://en.wikibooks.org/wiki/TeX/catcode

标记化之后,会有一个“标记流”。处理标记流包括扩展可扩展标记(例如,宏标记,例如可扩展基元,如\string\csname...\endcsname)以及(稍后)执行分配、创建框等。

在读取和标记 .tex 输入文件时,(La)TeX 将在预处理阶段删除每个行末的空格并在每个行末插入一个结束符。

因此输入序列

\immediate\write\file{
To be or not to be,
that is % the question
}

将在标记时(即在预处理之后)被 (La)TeX 视为

\immediate\write\file{⟨character due to endline-char-insertion⟩
To be or not to be,⟨character due to endline-char-insertion⟩
that is % the question⟨character due to endline-char-insertion⟩
}⟨character due to endline-char-insertion⟩

通常,结束符是^^M,即⟨返回⟩

因此,上述输入序列在标记时通常会被 (La)TeX 处理为

\immediate\write\file{⟨^^M/RETURN-character⟩
To be or not to be,⟨^^M/RETURN-character⟩
that is % the question⟨^^M/RETURN-character⟩
}⟨^^M/RETURN-character⟩

(当遇到⟨^^M/RETURN 字符⟩取决于在标记化时分配给⟨^^M/RETURN 字符⟩

通常类别代码⟨^^M/RETURN 字符⟩是 5(行尾),这意味着根据 (La)TeX 读取装置的状态(在状态 S=跳过空白时)根本没有标记,或者(在状态 M=在行中间)将插入一个空格标记(=类别代码 10(空格)和字符代码 32 的字符标记(32 是 (La)TeX 内部字符编码中空格字符的数量)或(在状态 N=即将开始新行时)\par将插入一个 -token。

如果类别代码为 12(其他)分配给⟨^^M/RETURN 字符⟩,(La)TeX 将插入类别代码为 12(其他)的字符标记和字符代码为 13 的字符标记(13 是⟨RETURN 字符⟩,在 (La)TeX 的内部字符编码中)进入标记流。这样的标记可以像任何其他字符标记一样进行处理。

除此之外,(La)TeX 在写入时无论如何都会在命令参数的末尾附加\write该平台上用于结束纯文本文件中的行的字符/字节序列。

因此 - 假设我们设法让 LaTeX 接受百分比字符作为普通字符 - 命令\write将得到类似如下的结果:

⟨token due to ^^M/RETURN-character⟩To be or not to be,⟨token due to ^^M/RETURN-character⟩that is % the question⟨token due to ^^M/RETURN-character⟩

At写作时间,

⟨platform-dependent sequence for ending the line⟩
将被附加。

如果结束符的类别代码/⟨^^M/RETURN 字符⟩在对输入进行标记时为 5(行尾),序列

⟨space⟩To be or not to be,⟨space⟩that is % the question⟨space⟩⟨platform-dependent sequence for ending the line⟩
将被写入外部文件。

如果结束符的类别代码/⟨^^M/RETURN 字符⟩在对输入进行标记时为 12(返回),序列

^^MTo be or not to be,^^Mthat is % the question^^M⟨platform-dependent sequence for ending the line⟩
将被写入外部文件。

您可以确保在写入时⟨^^M/RETURN 字符⟩还会产生 ⟨与平台相关的行尾结束序列⟩通过为整数参数分配\newlinechar整数参数的值\endlinechar

如果你也这样做,那么序列

⟨platform-dependent sequence for ending the line⟩To be or not to be,⟨platform-dependent sequence for ending the line⟩that is % the question⟨platform-dependent sequence for ending the line⟩⟨platform-dependent sequence for ending the line⟩

将被写入外部文件。

但这样你可能会得到不想要的空行。

因此,您可能希望应用一个例程来删除前导和尾随⟨由于行尾插入而产生的字符⟩\write在开始写作工作之前,先从整个论点入手。


编码示例可能如下所示:

\documentclass{article}

\makeatletter

\begingroup
\catcode`\^^M=12\relax%
\@firstofone{%
  \endgroup%
  \newcommand*\gobbleendl{}\def\gobbleendl ^^M{}%
  \newcommand\trimendls[2]{\innertrimleadendl{#2}#1^^M\relax{#1}}%
  \newcommand*\innertrimleadendl{}%
  \def\innertrimleadendl#1#2^^M#3\relax#4{%
    \ifx\relax#2\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi%
    {%
      \ifx\relax#4\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi%
      {\trimtrailendl{}{#1}}%
      {\expandafter\trimtrailendl\expandafter{\gobbleendl#4}{#1}}%
    }%
    {\trimtrailendl{#4}{#1}}%
  }%
  \newcommand*\trimtrailendl[2]{%
    \innertrimtrailendl{#2}.#1\relax.^^M\relax.\relax\relax{#1}%
  }%
  \newcommand*\innertrimtrailendl{}%
  \def\innertrimtrailendl#1#2^^M\relax.#3\relax\relax#4{%
    \ifx\relax#3\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi%
    {\def\@tempa{#4}}%
    {\expandafter\def\expandafter\@tempa\expandafter{\@gobble#2}}%
    \@onelevel@sanitize\@tempa%
    \newlinechar=\endlinechar%
    \immediate\write#1{\@tempa}%
  }%
}%

\newcommand\immediateverbatimwrite[1]{%
  \begingroup
  \let\do=\@makeother
  \dospecials
  \catcode`\ =10 %We don't want to allow space as verb-arg-delimiter.
                 %Thus let's remove spaces when grabbing undelimited arguments.
  %\endlinechar=`\^^M%
  %\catcode`\endlinechar=5 %
  \bracefork{#1}%
}%
\begingroup
\catcode`\(=1 %
\catcode`\{=12 %
\@firstofone(%
  \endgroup
  \newcommand\bracefork[2](%
    \catcode`\ =12\relax
    \catcode\endlinechar=12 %
    \ifx{#2\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    (%
      \catcode`\{=1 %
      \catcode`\}=2 %
      \internalfilewritercaller(#1}(}%
    }(%
      \internalfilewritercaller(#1}(#2}%
    }%
  }%
}%
\newcommand\internalfilewritercaller[2]{%
  \def\@tempa##1#2{\internalfilewriter{#1}{##1}}%
  \ifx\relax#2\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
  {\expandafter\expandafter
   \expandafter\@tempa
   \expandafter\expandafter
   \expandafter{%
   \expandafter\@gobble\string}}%
  {\@tempa}%
}
\newcommand\internalfilewriter[2]{%
  \trimendls{#2}{#1}%
  \endgroup
}%
\makeatother

\begin{document}

\newwrite\file
\immediate\openout\file=tmp.txt\relax

A\immediateverbatimwrite{\file}
{
être ou ne pas être.
That is % the question.
}B%
C%
%
D\immediateverbatimwrite{\file}  |
}être ou ne pas être.
That is % the question.
|E%
F

\immediate\closeout\file

\end{document}

通过此示例您可以获得

  • 具有序列 ABCDEF 的 pdf 文件。(这表明没有引入/插入任何虚假空格/任何字符。)
  • 一个名为的文本文件临时文件其内容如下:
    être ou ne pas être.⟨linebreak⟩
    That is % the question.⟨linebreak⟩
    }être ou ne pas être.⟨linebreak⟩
    That is % the question.⟨linebreak⟩
    由于换行符,显示行号的编辑器可能会将该文件显示为
    1 être ou ne pas être.
    2 That is % the question.
    3 }être ou ne pas être.
    4 That is % the question.
    5

顺便说一句:使用 (La)TeX不是可以在行尾保留空格。

原因是 (La)TeX 确实逐行读取和标记输入,并且它对每一行输入所做的第一件事(在预处理阶段)(甚至在添加结束行字符和开始标记行之前)是删除行尾的所有空格。

因此 (La)TeX 输入如下

code⟨space⟩⟨space⟩
more code⟨space⟩⟨space⟩⟨space⟩⟨space⟩⟨space⟩
even more code⟨space⟩⟨space⟩

在任何情况下都会被预处理为

code⟨character due to endline-char-insertion⟩more code⟨character due to endline-char-insertion⟩even more code⟨character due to endline-char-insertion⟩

在进行任何进一步的处理/标记化等之前。

相关内容