将文件内容读入变量并迭代文件中的每个字符(十六进制转储)

将文件内容读入变量并迭代文件中的每个字符(十六进制转储)

我需要将文件内容作为字符串(二进制文件)读取到变量中,然后遍历该字符串中的每个字符,将每个字符转换为其十六进制表示形式,使用逗号(或任何其他可区分的字符)作为字符分隔符,并将结果嵌入到我的实际中乳胶我正在进行所有这些事情的文件。

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@filedumpfrom 来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。可选参数(默认为空格)用于控制两个字节之间的分隔符。

图中显示了第一次通话的最后两行和第二次通话的前两行。

在此处输入图片描述

可以轻松添加对文件存在的检查。

相关内容