我有一个在 unix shell 中生成的日志文件,其中包含一堆 shell/终端颜色转义序列。例如,echo -e '\e[1;34mBLUE TEXT\e[0m'
以蓝色输出“蓝色文本”等等。如何将此日志文件插入我的 LaTeX 文档,并确保生成的 PDF 中的文本与终端中的文本完全相同(保留颜色)?
需要澄清的是:echo
上面是如何生成彩色输出的示例。我需要插入的是结果输出,而不是命令echo
本身,因此\e
实际上是带有十六进制代码的文字转义字节1B
。
示例输出:
https://www.filedropper.com/typescript
它的实际外观的图片(或者你可以cat
在 unix 终端中看到它):
答案1
这是您所要求的近似值。
TeX 不提供可靠地输入二进制文件的方法,但对于 pdfTeX,有一个\pdffiledump
读取二进制文件的原语,将其转义为十六进制数字序列。我们首先需要预处理此字符串以获取字符序列,每个字符的 catcode 为 12 ( \term@preprocess
)。
然后我们解析这个输入字符串 ( \term@process
),在标记列表中构建当前输出行\term@line
(出于技术原因,以相反的顺序构建)。根据当前输入字符,输出标记列表中的元素将被附加、删除或打印,并开始新的标记列表。
每当出现转义字符 ( \x1B
) 时,下一个字符决定转义序列的类型。如果是[
,后面跟着一系列参数编号,以及指定终端输出类型的最后一个字符。然后,我们根据此类型和给定的参数编号来修改当前显示属性的全局变量 ( \term@process@termout
)。每当向当前输出行添加新字符时,都会考虑后者。
完整代码:
\documentclass{article}
\usepackage{xcolor}
\usepackage{pdftexcmds}
\usepackage{mdframed}
\usepackage[utf8]{inputenc}
\usepackage{textcomp}
\makeatletter
\endlinechar=-1
% Manipulation of current output line
\newif\ifterm@cr@
\def\term@line{}
\def\term@line@push#1{
\ifterm@cr@
\def\term@line{}
\fi
\xdef\term@line{
{{\noexpand\strut
\noexpand\color{\term@fgcolor\ifterm@intense@!75!white\fi}
\unexpanded{#1}}}
\unexpanded\expandafter{\term@line}
}
\term@cr@false
}
\def\term@line@pop{
\xdef\term@line{\expandafter\unexpanded\expandafter\expandafter\expandafter
{\expandafter\@gobble\term@line}}
\term@cr@false
}
\def\term@line@print{
\leavevmode
\expandafter\term@line@reverse\expandafter\@sep\term@line{}\@end
}
\def\term@line@reverse#1\@sep#2#3\@end{
\if@empty{#3}{
#2#1
}{
\term@line@reverse#2#1\@sep#3\@end
}
}
% Display attributes
\def\term@fgcolor{}
\def\term@default@fgcolor{lightgray}
\newif\ifterm@intense@
% Input parsing
\newcount\term@param@a
\newcount\term@param@b
\begingroup
\count0=0\relax
\loop
\lccode`\*=\count0\relax
\lowercase{
\expandafter\gdef\csname term@preprocess@char@\number\count0\endcsname{*}
}
\advance\count0 by 1\relax
\ifnum\count0<256\relax
\repeat
\endgroup
\def\term@preprocess#1#2{
\if@empty{#1}{}{
\csname term@preprocess@char@\number"#1#2\endcsname
\term@preprocess
}
}
\def\term@process#1{
% End of input
\if@char@eq#1\relax{
\@@par
\condition@true{}
}{}
% Escape sequence
\if@char@eq#1\term@escape@char{
\condition@true\term@process@esc
}{}
% Newline
\if@char@eq#1\term@lf@char{
\term@line@print
\@@par
\condition@true\term@process
}{}
% Carriage return
\if@char@eq#1\term@cr@char{
\term@cr@true
\condition@true\term@process
}{}
\if@char@eq#1\term@backspace@char{
\term@line@pop
\condition@true\term@process
}{}
\if@char@eq#1\term@space@char{
\term@line@push{\ }
\condition@true\term@process
}{}
\if@num@range{`#1}{194}{223}{
\condition@true{\term@process@unicode@ii#1}
}{}
\if@num@range{`#1}{224}{239}{
\condition@true{\term@process@unicode@iii#1}
}{}
\if@num@range{`#1}{240}{244}{
\condition@true{\term@process@unicode@iv#1}
}{}
\condition@false{
\term@line@push{#1}
\term@process
}
}
\def\term@process@esc#1{
\if@char@eq#1[{
\term@process@csi
}{
\GenericWarning{}{Warning: Ignored unknown escape sequence of type `#1'}
\term@process
}
}
\def\term@process@csi#1{
\term@param@b=-1\relax
% Is private sequence?
\if@char@eq#1?{
\afterassignment\term@process@csi@
\term@param@a=0
}{
\afterassignment\term@process@csi@
\term@param@a=0#1
}
}
\def\term@process@csi@#1{
% Check for second parameter
\if@char@eq#1;{
\afterassignment\term@process@termout
\term@param@b=0
}{
\term@process@termout #1
}
}
\def\term@process@termout#1{
% SGR parameter
\if@char@eq#1m{
\term@process@sgr\term@param@a
\ifnum\term@param@b<0\else
\term@process@sgr\term@param@b
\fi
\condition@true{}
}{}
\condition@false{
% \GenericWarning{}{Warning: Ignored unknown terminal output sequence of type `#1'}
}
\term@process
}
\def\term@process@sgr#1{
% Reset
\if@num@eq{#1}{0}{
\let\term@fgcolor=\term@default@fgcolor
\term@intense@false
\condition@true{}
}{}
% Bold/intense
\if@num@eq{#1}{1}{
\term@intense@true
\condition@true{}
}{}
% Standard colors
\if@num@eq{#1}{30}{
\def\term@fgcolor{black}
\condition@true{}
}{}
\if@num@eq{#1}{31}{
\def\term@fgcolor{red}
\condition@true{}
}{}
\if@num@eq{#1}{32}{
\def\term@fgcolor{green}
\condition@true{}
}{}
\if@num@eq{#1}{33}{
\def\term@fgcolor{yellow}
\condition@true{}
}{}
\if@num@eq{#1}{34}{
\def\term@fgcolor{blue}
\condition@true{}
}{}
\if@num@eq{#1}{35}{
\def\term@fgcolor{magenta}
\condition@true{}
}{}
\if@num@eq{#1}{36}{
\def\term@fgcolor{cyan}
\condition@true{}
}{}
\if@num@eq{#1}{37}{
\def\term@fgcolor{white}
\condition@true{}
}{}
\condition@false{}
}
% n-byte Unicode sequences
\def\term@process@unicode@ii#1#2{
\scantokens{\csname term@line@push\endcsname{#1#2}}
\term@process
}
\def\term@process@unicode@iii#1#2#3{
\scantokens{\csname term@line@push\endcsname{#1#2#3}}
\term@process
}
\def\term@process@unicode@iv#1#2#3#4{
\scantokens{\csname term@line@push\endcsname{#1#2#3#4}}
\term@process
}
% Helper macros
\def\if@empty#1{
\if\relax\detokenize{#1}\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\def\if@char@eq#1#2{
\if#1#2%
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\def\if@num@eq#1#2{
\ifnum#1=#2 %
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\def\if@num@range#1#2#3{
\ifnum#1<#2 %
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{
\@secondoftwo
}{
\ifnum#1>#3 %
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi
}
}
\def\condition@true#1#2\condition@false#3{#1}
\def\condition@false#1{#1}
\def\@temp#1#2{
\begingroup
\lccode`\*=`#2
\lowercase{\global\let#1=*}
\endgroup
}
\@temp\term@backspace@char \^^H
\@temp\term@lf@char \^^J
\@temp\term@cr@char \^^M
\@temp\term@escape@char \^^[
\@temp\term@space@char \ %
% User macros
% Print terminal session stored in #1
\newcommand\terminalinput[1]{
\begin{mdframed}[
backgroundcolor=black,
innerleftmargin=0pt,
innerrightmargin=0pt,
innertopmargin=0pt,
innerbottommargin=0pt
]
\begingroup
\parindent=0pt
\frenchspacing
\ttfamily
\fboxsep=0pt
\term@process@sgr{0}
\xdef\@temp{\pdf@filedump{0}{\pdf@filesize{#1}}{#1}}
\expandafter\xdef\expandafter\term@input\expandafter{
\expandafter\term@preprocess\@temp{}{}
}
\expandafter\term@process\term@input\relax
\endgroup
\end{mdframed}
}
\endlinechar=`\^^M
\makeatother
\DeclareUnicodeCharacter{279C}{\textrightarrow}
\begin{document}
\terminalinput{typescript.bin}
\end{document}
输出
目前的实施还存在几个问题:
- 该代码仅适用于
pdflatex
或lualatex
编译器,因为它分别依赖于\pdffiledump
原语或其在 Lua 代码中的重新实现。 目前不支持 Unicode 字符,因为文件被解码为单字节流。可打印 ASCII 范围之外的字符目前将被忽略。
编辑:现在支持 Unicode 字符。每当遇到 UTF-8 2/3/4 字节序列时,字节都会通过 重新映射到其标准 catcode,\scantokens
以便inputenc
可以正常完成其工作。可以通过 添加新的字符映射\DeclareUnicodeCharacter
。- “终端窗口”目前只是一个横跨整个文本宽度的黑框。
- 当前实现仅涵盖所有可用转义序列/终端输出序列的一小部分。最值得注意的是,背景颜色根本没有处理,只有粗体/浓色的处理(通过附加
!75!white
到当前颜色),只有八种标准颜色(代码 30-37)被实现等等。 - 光标定位也处理不正确,例如回车符(
\0x0D
)清除整行而不是仅将光标移动到行首。 - 在终端输出序列中,仅支持一个或两个参数数量,但应该支持任意数量或参数。
- 代码尚未经过很好的测试,实际上仅使用给定的示例文件进行测试。;-)
答案2
比如说,像这样。
\documentclass{article}
\usepackage{fancyvrb}
\usepackage{color}
\def\defaultcode{[0}
\def\bluecode{[1;34}
\def\redcode{[1;31}
\makeatletter
\def\e#1m{%
\def\colcode{#1}%
\ifx\colcode\defaultcode\color{black}%
\else\ifx\colcode\bluecode\color{blue}%
\else\ifx\colcode\redcode\color{red}%
\fi\fi\fi}
\makeatother
\begin{document}
\begin{Verbatim}[commandchars=\\\{\}]
echo -e '\e[1;34mBLUE TEXT\e[0m'
echo -e '\e[1;31mRED TEXT\e[0m'
echo -e 'DEFAULT COLOURED TEXT'
\end{Verbatim}
\end{document}
控制代码以 开头\e
并以 结尾m
。因此,我们选取介于 之间的所有内容,然后将其与一些预定义代码进行比较。添加其他代码很容易,例如,绿色将由 来支持
\def\greencode{[1;32}
进而
\else\ifx\colcode\greencode\color{green}
\fi
最后再添加一个。之后,你只需要一个允许宏的逐字环境。在这里我使用了fancyvrb 包。请注意,您需要更改颜色
\ifx\colcode\defaultcode\color{black}
如果您的默认颜色不是黑色。