我需要将文件内容作为字符串(二进制文件)读取到变量中,然后遍历该字符串中的每个字符,将每个字符转换为其十六进制表示形式,使用逗号(或任何其他可区分的字符)作为字符分隔符,并将结果嵌入到我的实际中乳胶我正在进行所有这些事情的文件。
在Python我有类似的东西(4行!):
fileContent = ""
with open("binary.so", "rb") as f:
fileContent = f.read()
print( ",".join( map(lambda x: str(ord(x)), fileContent) ) )
现在到了困难的部分:
- 没有额外的包 = 只有 tex 原语
- 定义为tex 宏:
\readfile{ input_file_here }
- 必须与pdflatex
- 文件可能为空
- 必须保留换行符和其他特殊字符(它是二进制的!)
- 我需要检查它是否真的是一个文件而不是一个目录,以及我是否被允许读取这样的文件
- 应该可以在 Linux/Unix 和 Windows 上运行(实际上我只需要 Linux,但更通用的解决方案就更好了)
- 序言中没有额外的定义
- 不
luatex
,不immediate
,所以没有 shell 也没有额外的工具
因为这是一个二进制文件(我需要这样处理所有文件),所以我们的字符值介于 0(0x00)和 255(0xFF)之间。数字可能是十六进制,也可能只是十进制。base64编码也可以,但是没有外部工具和额外的标志,例如--shell-escape
。
\readfile{binary.so} % will return something like "D,E,A,D,B,E,E,F,1,3,3,7" or "0x0D,0x0E,..." or "13,14,..."
我尝试了类似的方法,但没有奏效:(示例取自这里)
\newread\makerfile
\openin\makerfile=binary.so
\ifeof\makerfile
\else
% it looks promising, but it shouldn't read by newlines
\read\makerfile to\makerline
\closein\makerfile
% iterate over each character
% ...
\fi
例如,这是可行的,但我不能迭代字符,因为我不知道如何
\makeatletter
\newcommand\saferead[1] {
\bgroup
\let\do\@makeother
\dospecials
\catcode`\ =10 % spaces
\catcode`\^^M=\active % newlines
\input{#1}
\egroup
}
\makeatother
我疯狂地在 Google 上搜索,只找到了一些有用的信息,但距离我想要的还很远。
答案1
正如 Heiko Oberdiek 指出的那样在这个答案中pdfTeX 定义了一个新的可扩展原语\pdffiledump
,可用于以二进制模式读取文件。该命令的语法是
\pdffiledump offset 0 length <length>{<filename>}
其中,对于<length>
我们可以使用另一个原语\pdffilesize{<filename>}
。结果是对的序列XX
,其中XX
是输入文件中每个字符的十六进制表示。其余处理与下面的答案类似,除了我们不需要额外的十六进制转换。
\documentclass{article}
\makeatletter
\def\showbinary#1{%
\begingroup
\xdef\@temp{\pdffiledump offset 0 length \pdffilesize{#1}{#1}}%
\expandafter\analyze\expandafter{\@temp}%
\endgroup
}
\def\analyze#1{%
\count@=0
\if\relax\detokenize{#1}\relax\else
\expandafter\analyze@#1\@end
\fi
}
\def\analyze@#1#2#3\@end{%
#1#2
\advance\count@ by 1
\ifnum\count@>15
\count@=0
\par
\fi
%
\let\@next=\relax
\if\relax\detokenize{#3}\relax\else
\def\@next{\analyze@#3\@end}%
\fi
\@next
}
\makeatother
\begin{document}
\ttfamily
\showbinary{ascii.txt}
\end{document}
输出
旧答案
虽然不是一个完整的答案,但这是我能想到的读取二进制文件的最佳答案:
\documentclass{article}
\makeatletter
\def\showbinary#1{%
\begingroup
\count@=0
\loop
\catcode\count@=12
\advance\count@ by 1
\ifnum\count@<256
\repeat
%
\endlinechar=-1
\everyeof{\noexpand}%
\xdef\@temp{\@@input #1 }%
%
\analyze\@temp
\endgroup
}
\def\analyze#1{%
\expandafter\analyze@#1\@end
}
\def\analyze@#1#2\@end{%
\count@=`#1\relax
\expandafter\hex\expandafter{\the\count@}
\let\@next=\relax
\if\relax\detokenize{#2}\relax\else
\def\@next{\analyze@#2\@end}%
\fi
\@next
}
\def\hex#1{%
\begingroup
\count@=#1\relax
\divide\count@ by 16
\hexchar\count@
%
\multiply\count@ by 16
\advance\count@ by -#1\relax
\multiply\count@ by -1
\hexchar\count@
\ifnum\count@=15\par\fi
\endgroup
}
\def\hexchar#1{%
\ifcase#10\or1\or2\or3\or4\or5\or6\or7\or8\or9\or A\or B\or C\or D\or E\or F\else x\fi
}
\makeatother
\begin{document}
\ttfamily
\showbinary{ascii.txt}
\end{document}
输出
ascii.txt
是一个二进制文件,包含从 0x00 到 0xFF 的所有字符。首先,将所有这些字符设置为 catcode 12(其他),然后对文件进行\input
处理并将其内容存储在宏中\@temp
。之后,我们迭代每个字符以\@temp
输出其十六进制表示形式。
如您所见,缺少三个字符:0x09 ( \t
)、0x0A ( \n
) 和 0x0D ( \r
)。后两个字符可能是因为 TeX 文件是在文本模式下读取的,而不是在二进制模式下读取的。不确定是否可以做些什么来解决这个问题。这个特定的测试文件中缺少制表符,因为当制表符出现在行末时(紧接着是\t
), TeX 会将其视为空格\n
,因此会将其从输入行中删除。
答案2
你使用的是 LaTeX,而不是 Plain,因此不是使用包。使用一些expl3
代码,您就可以创建一个适当的hexdump
文件。
我之前的回答(见编辑历史)使用了一个相当简单的expl3
代码来读取文件并hexdump
对其进行处理。但是代码相当慢(它花了大约 60 秒来生成 6 kB 文件的 7 页十六进制转储)。
我做了一个稍微优化的版本(处理同一个文件大约需要半秒钟:-),增加了一些细节:速度更快,有一些 key-val 属性来控制输出,速度更快,使用\pdf@filedump
from 来pdftexcmds
避免丢失换行符和空格,而且很多快点 :-)
这里是:
\documentclass{article}
\usepackage{pdftexcmds}
\usepackage{xparse}
\ExplSyntaxOn
\cs_new_eq:Nc \__hexdump_filedump:nnn { pdf@filedump }
\cs_new_eq:Nc \__hexdump_filesize:n { pdf@filesize }
\int_new:N \l__hexdump_begin_int
\int_new:N \l__hexdump_bytes_int
\int_new:N \l__hexdump_filesize_int
\int_new:N \l__hexdump_byte_int
\int_new:N \l__hexdump_byte_ptr_int
\int_new:N \l__hexdump_word_int
\int_new:N \l__hexdump_word_ptr_int
\int_new:N \l__hexdump_column_int
\int_new:N \l__hexdump_column_ptr_int
\int_new:N \l__hexdump_line_length_int
\int_new:N \l__hexdump_address_size_int
\int_new:N \l__hexdump_address_int
\bool_new:N \l__hexdump_address_bool
\tl_new:N \l__hexdump_dump_tl
\tl_new:N \l__hexdump_font_tl
\tl_new:N \l__hexdump_visible_tl
\clist_new:N \l__hexdump_cols_clist
\seq_new:N \l__hexdump_cols_seq
\cs_generate_variant:Nn \str_count:n { f }
\keys_define:nn { hexdump }
{
, begin .int_set:N = \l__hexdump_begin_int
, begin .initial:n = { 0 }
, length .int_set:N = \l__hexdump_bytes_int
, length .initial:n = { -1 }
, byte .int_set:N = \l__hexdump_byte_int
, byte .initial:n = { 2 }
, columns .clist_set:N = \l__hexdump_cols_clist
, columns .initial:n = { 4, 4 }
, font .tl_set:N = \l__hexdump_font_tl
, font .initial:n = \ttfamily
}
\NewDocumentCommand \hexdump { o m }
{
\group_begin:
\IfValueT {#1} { \keys_set:nn { hexdump } {#1} }
\hexdump:n {#2}
\group_end:
}
\cs_new_protected:Npn \hexdump:n #1
{
\file_if_exist:nTF {#1}
{ \__hexdump_read:n {#1} }
{ \msg_error:nnn { hexdump } { file-not-found } {#1} }
}
\cs_new_protected:Npn \__hexdump_read:n #1
{
\int_set:Nn \l__hexdump_filesize_int { \__hexdump_filesize:n {#1} }
\__hexdump_assert_int:Nnn \l__hexdump_begin_int
{ \c_zero_int } { \l__hexdump_filesize_int }
\int_compare:nNnT { \l__hexdump_bytes_int } = { -1 }
{ \int_set:Nn \l__hexdump_bytes_int { \l__hexdump_filesize_int } }
{
\__hexdump_assert_int:Nnn \l__hexdump_bytes_int
{ \c_zero_int } { \l__hexdump_filesize_int }
}
\tl_set:Nx \l__hexdump_dump_tl
{
\__hexdump_filedump:nnn
{ \l__hexdump_begin_int } { \l__hexdump_bytes_int }
{#1}
}
\tl_map_function:nN { \. \? \! \: \; \, } \__hexdump_french_spacing:N
\tl_use:N \l__hexdump_font_tl
\__hexdump:N \l__hexdump_dump_tl
}
\cs_new_protected:Npn \__hexdump_french_spacing:N #1
{ \char_set_sfcode:nn { `#1 } { 1000 } }
\cs_new_protected:Npn \__hexdump_assert_int:Nnn #1 #2 #3
{ \int_set:Nn #1 { \int_min:nn { \int_max:nn { #1 } { #2 } } { #3 } } }
\msg_new:nnn { hexdump } { file-not-found }
{ File~`#1'~not~found. }
\cs_new_protected:Npn \__hexdump:N #1
{
\__hexdump_initialise:
\exp_last_unbraced:NV \__hexdump:NNw #1
\q_recursion_tail \q_recursion_tail \q_recursion_stop
}
\cs_new_protected:Npn \__hexdump_initialise:
{
\seq_set_from_clist:NN \l__hexdump_cols_seq \l__hexdump_cols_clist
\int_set:Nn \l__hexdump_word_int { \seq_item:Nn \l__hexdump_cols_seq { 1 } }
\int_set:Nn \l__hexdump_column_int { \seq_count:N \l__hexdump_cols_seq }
\int_set:Nn \l__hexdump_address_size_int
{ \str_count:f { \int_to_hex:n { \l__hexdump_bytes_int } } }
\int_set_eq:NN \l__hexdump_address_int \l__hexdump_begin_int
\int_set:Nn \l__hexdump_line_length_int
{ \l__hexdump_byte_int * ( \seq_use:Nn \l__hexdump_cols_seq { + } ) }
\exp_args:NNf \seq_put_right:Nn \l__hexdump_cols_seq
{ \seq_item:Nn \l__hexdump_cols_seq { 1 } }
\bool_set_true:N \l__hexdump_address_bool
\int_zero:N \l__hexdump_byte_ptr_int
\int_zero:N \l__hexdump_word_ptr_int
\int_zero:N \l__hexdump_column_ptr_int
}
\cs_new_protected:Npn \__hexdump:NNw #1 #2
{
\quark_if_recursion_tail_stop_do:Nn #1
{ \__hexdump_end: }
\bool_if:NT \l__hexdump_address_bool { \__hexdump_address: }
#1 #2
\tl_put_right:Nx \l__hexdump_visible_tl
{
\__hexdump_if_visible_ascii:nTF { "#1#2 }
{ \char_generate:nn { "#1#2 } { 12 } }
{ . }
}
\__hexdump_ptr_check:
\__hexdump:NNw
}
\cs_new_protected:Npn \__hexdump_ptr_check:
{
\__hexdump_ptr_step:nn { byte }
{
\c_space_tl
\__hexdump_ptr_step:nn { word }
{
\int_set:Nn \l__hexdump_word_int
{
\seq_item:Nn \l__hexdump_cols_seq
{ \l__hexdump_column_ptr_int + 2 }
}
\c_space_tl
\__hexdump_ptr_step:nn { column }
{ \tex_unskip:D \__hexdump_dump_visible: }
}
}
}
\cs_new_protected:Npn \__hexdump_ptr_step:nn #1 #2
{
\int_incr:c { l__hexdump_#1_ptr_int }
\int_compare:nNnT
{ \int_use:c { l__hexdump_#1_ptr_int } }
=
{ \int_use:c { l__hexdump_#1_int } }
{
\int_zero:c { l__hexdump_#1_ptr_int }
#2
}
}
\prg_new_protected_conditional:Npnn \__hexdump_if_visible_ascii:n #1 { TF }
{
\int_compare:nNnTF {#1} > {31}
{
\int_compare:nNnTF {#1} < {127}
{ \prg_return_true: }
{ \prg_return_false: }
}
{ \prg_return_false: }
}
\cs_new_protected:Npn \__hexdump_address:
{
\bool_set_false:N \l__hexdump_address_bool
\exp_args:Nf \__hexdump_address:nn
{ \str_count:f { \int_to_hex:n { \l__hexdump_address_int } } }
{ \l__hexdump_address_size_int }
\int_add:Nn \l__hexdump_address_int { \l__hexdump_line_length_int }
}
\cs_new_protected:Npn \__hexdump_address:nn #1 #2
{
\prg_replicate:nn { #2 - #1 } { 0 }
\int_to_hex:n { \l__hexdump_address_int } : ~
}
\cs_new_protected:Npn \__hexdump_dump_visible:
{
| \tl_use:N \l__hexdump_visible_tl |
\tl_clear:N \l__hexdump_visible_tl
\bool_set_true:N \l__hexdump_address_bool
\tex_par:D
}
\cs_new_protected:Npn \__hexdump_end:
{
\bool_if:NF \l__hexdump_address_bool
{
\c_space_tl \c_space_tl
\tl_put_right:Nn \l__hexdump_visible_tl { ~ }
\__hexdump_ptr_check:
\__hexdump_end:
}
}
\ExplSyntaxOff
\begin{document}
\hexdump{somebinary.file}
\end{document}
.
可见字节(ASCII 32 – 126)被打印,其他所有内容都由右侧窗格中的表示:
答案3
这是一个可扩展的版本(使用两个“禁用功能”),采用了锡拉库萨的想法。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewExpandableDocumentCommand{\hexdump}{O{~}m}
{
\awa_hexdump:ne {#1} { \tex_filedump:D~offset~0~length~\tex_filesize:D{#2}{#2} }
}
% there's not yet an official interface to \pdffiledump and \filesize
\cs_new:Nn \awa_hexdump:nn
{
\__awa_hexdump_read_byte:nNNN {#1} #2 \q_nil \q_stop
}
\cs_generate_variant:Nn \awa_hexdump:nn { ne }
\cs_new:Nn \__awa_hexdump_read_byte:nNNN
{
\quark_if_nil:nTF { #4 }
% true: print the last two digits and ignores the trailer
{ #2#3 \use_none:n }
% false: print two digits, a comma and some space
{ #2#3#1 \__awa_hexdump_read_byte:nNNN { #1 } #3 }
}
\ExplSyntaxOff
\begin{document}
\raggedright\ttfamily
\hexdump{cmr10.tfm}
\hexdump[,\hspace{0pt plus 1fill}]{\jobname.tex}
\end{document}
我使用了标准文件的副本cmr10.tfm
。可选参数(默认为空格)用于控制两个字节之间的分隔符。
图中显示了第一次通话的最后两行和第二次通话的前两行。
可以轻松添加对文件存在的检查。