如何查询文件是否已更改( \IfFileHasChanged 条件)?

如何查询文件是否已更改( \IfFileHasChanged 条件)?

我们知道\IfFileExists{<file>}{<yes>}{<no>}

如何\IfFileHasChanged{<file>}{<yes>}{<no>}
为已更改(并且确实存在!)的文件创建命令。

我的意思是:

  • 如果有的myfile.txtThis is my file.应该写在第一个(带filecontents)。

  • 如果我改了myfile.txtNow this is my file...它应该是新写的。

  • 否则:不写新的。

也许这与“时间戳”有关。

\begin{filecontents*}[overwrite]{myfile.txt}
This is my file.
\end{filecontents*}

\documentclass{article}
\begin{document}
\section{IfFileExists}
\IfFileExists{myfile.txt}{\input{myfile.txt}}{Does not exist!}

\section{IfFileHasChanged}
\end{document}

答案1

正如 Ulrike 所说,检查文件是否已更改需要将其与文件的先前状态进行比较。

这里有一个实现,使用与 Rmano 的答案大致相同的方法,将文件的 MD5 总和存储在文件中.aux,并且还将每个文件一个 MD5 存储在属性列表中,以便您可以拥有多个文件。

\IfFileChangedTF通过查询文件的 MD5 并将其与先前已知的值进行比较,检查文件相对于先前已知的状态是否已发生更改,并相应地返回<false><true>。第一次检查文件时,不知道 MD5,因此条件也会返回<true>

但是,条件语句有副作用。当<true>执行分支时,属性列表将使用新的 MD5 和更新,这意味着两次连续运行该命令可能会产生不同的结果。第一次运行尤其如此:第一次执行时\IfFileChangedTF{some-file},命令不知道some-file,因此它会存储 MD5 和并返回<true>。但是,下次运行时\IfFileChangedTF{some-file},文件已经已知,因此如果它没有更改,条件语句将返回<false>

此外,由于状态存储在中,因此第一次调用文件.aux之前始终会返回。\begin{document}\IfFileChangedTF<true>

运行示例文档一次将生成(一个“ Didn't change”):

在此处输入图片描述

再次运行它会产生(两个“ Didn't change”):

在此处输入图片描述

\begin{filecontents*}[overwrite]{myfile.txt}
This is my file.
\end{filecontents*}

\documentclass{article}
\usepackage{xparse}
\pagestyle{empty}
\ExplSyntaxOn
\prop_new:N \g__cis_file_mdfive_prop
\tl_new:N \l__cis_tmpa_str
\tl_new:N \l__cis_tmpb_str
\NewDocumentCommand \IfFileChangedTF { m +m +m }
  { \cis_file_if_changed:nTF {#1} {#2} {#3} }
\prg_new_protected_conditional:Npnn \cis_file_if_changed:n #1 { T, F, TF }
  {
    \file_if_exist:nTF {#1}
      {
        \file_get_mdfive_hash:nN {#1} \l__cis_tmpb_str
        \prop_get:NnNTF \g__cis_file_mdfive_prop {#1} \l__cis_tmpa_str
          {
            \str_if_eq:NNTF \l__cis_tmpa_str \l__cis_tmpb_str
              { \prg_return_false: }
              {
                \__cis_mdfive_update:nN {#1} \l__cis_tmpb_str
                \prg_return_true:
              }
          }
          {
            \__cis_mdfive_update:nN {#1} \l__cis_tmpb_str
            \prg_return_true:
          }
      }
      { \msg_error:nnn { cis } { file-not-found } {#1} }
  }
\makeatletter
\cs_new_protected:Npn \cis@mdfive@update #1 #2
  { \prop_gput:Nnx \g__cis_file_mdfive_prop {#1} {#2} }
\cs_new_protected:Npn \cis@mdfive@save #1 #2
  { \iow_now:Nx \@auxout { \exp_not:N \cis@mdfive@update {#1} {#2} } }
\cs_new_protected:Npn \__cis_mdfive_update:nN #1 #2
  { \cis@mdfive@update {#1} {#2} }
\AtEndDocument
  {
    \prop_map_inline:Nn \g__cis_file_mdfive_prop
      { \cis@mdfive@save {#1} {#2} }
  }
\makeatother
\msg_new:nnn { cis } { file-not-found }
  { File~'#1'~not~found. }
\ExplSyntaxOff

\begin{document}
\section{IfFileExists}
\IfFileExists{myfile.txt}{\input{myfile.txt}}{Does not exist!}

\section{IfFileChangedTF}
\IfFileChangedTF{myfile.txt}{\input{myfile.txt}}{Didn't change!}

\IfFileChangedTF{myfile.txt}{\input{myfile.txt}}{Didn't change!}
\end{document}

答案2

我假设情况如下。我们有一个 LaTeX 文件,称为filechanged.tex,使用 运行pdflatex filechanged。在同一目录中,我还有另一个文件myfile.txt

当我运行时pdflatex filechanged,我想根据myfile.txt上次运行后是否发生变化来执行不同的处理。

我的解决方案:

  1. 它基于@Skillmon 建议在这里@egreg 代码在这里

  2. 它将在.aux文件(在运行开始时自动读取)上写入文件的 MD5 校验和的值myfile.txt

  3. 它将检查自上次运行以来是否发生了变化,如果是或否,则执行不同的操作;

  4. 它需要一个相当新的 LaTeX 发行版(不知道什么时候\file_get_mdfive_hash:nN推出的)

  5. 这是我的第一个 LaTeX3 程序,所以它可能充满错误。

似乎在这里有效...所以你有两个文件,第一个是mytext.txt

something here

然后是主文件:

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
    \cs_new:Npn \dobold #1
    {
        \textbf{#1}
    }
    \str_new:N \g_myfile_name
    \str_gset:Nn \g_myfile_name {myfile.txt}
    \str_new:N \g_myfile_old_mdfive
    \str_new:N \l_myfile_mdfive
    \cs_new:Npn \getmdfive
    {
        \file_get_mdfive_hash:nN {\str_use:N \g_myfile_name} \l_myfile_mdfive
        \str_use:N \l_myfile_mdfive
    }
    \cs_new:Npn \getoldmdfive
    {
        \str_use:N \g_myfile_old_mdfive
    }
    \cs_new:Npn \IfMyfileChanged #1 #2
    {
        \str_if_eq:NNTF \g_myfile_old_mdfive \l_myfile_mdfive {#2} {#1}
    }
    \AtEndDocument
    {
        \iow_now:cx { @auxout }
        {
            \token_to_str:N \ExplSyntaxOn
            ^^J
            \str_gset:Nn \token_to_str:N \g_myfile_old_mdfive {\str_use:N \l_myfile_mdfive}
            ^^J
            \token_to_str:N \ExplSyntaxOff
        }
    }
\ExplSyntaxOff

\begin{document}

MD5 was \getoldmdfive

MD5 is now \getmdfive

Changed? \IfMyfileChanged{Yes, it has changed}{No, it's the same as before}

\end{document}

答案3

我可以提供一个DifferentFileContents具有与 -environment 相同语法的环境filecontents*

-environment的内容DifferentFileContents将与指定文件的内容进行比较。

如果内容不同或指定的文件不存在,该文件将被销毁并根据环境的内容重新改写/创建。

这可能有助于减少固态硬盘上的写入操作量。

内部filecontents*使用 -environment 来实现这一点。

如果在调用“-environment ”时存在可选参数,DifferentFileContents则将其交给“ filecontents*-environment”。

如果filecontents*-environment(LaTeX 2ε-release v1.3c,2019/09/11 及更新版本)确实处理可选参数,那么一切应该没问题。

如果filecontents*-environment(LaTeX 2ε 版本 v1.3c 之前,2019/09/11)不处理可选参数,则[可选参数的左方括号将被视为要创建的文件的名称,并尝试创建此类文件。可选参数的其余部分和非可选的文件名参数将导致各种错误消息。

这意味着:

如果您使用的 LaTeX 2ε-release 中的filecontents*-environment 处理可选参数(LaTeX 2ε-release v1.3c,2019/09/11 及更新版本),那么 -environmentDifferentFileContents也可以处理可选参数。

如果您使用的 LaTeX 2ε 版本中的filecontents*-environment 不处理可选参数(LaTeX 2ε 版本早于 v1.3c,2019/09/11),那么 -environmentDifferentFileContents也无法处理可选参数。


下面的示例创建一个文件myfile.txt

因此,已经存在的文件myfile.txt可能会被破坏/覆盖。

编译日志文件后,终端将包含消息,通知用户环境内容和文件内容是否相同,因此文件未重新创建 / 环境内容和文件内容是否不同,因此文件重新创建。

\documentclass{article}
\usepackage{filecontents}

\makeatletter
\newcommand\PassFirstToSecond[2]{#2{#1}}%
\newcommand\Exchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% Stringify the first token of the second argument:
%%.............................................................................
\newcommand\UD@StringifySecond[2]{%
  \expandafter\PassFirstToSecond\expandafter{\string#2}{#1}%%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
  {\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%  \romannumeral0\UD@ExtractFirstArgLoop{ABCDE\UD@SelDOm} yields  {A}
%%
%%  \romannumeral0\UD@ExtractFirstArgLoop{{AB}CDE\UD@SelDOm} yields  {AB}
%%.............................................................................
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
  \long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
\newcommand\DifferentFilecontentsVerbatimcatcodes{%
  \let\do=\@makeother
  \dospecials
  \catcode\endlinechar=12 %
  \catcode`\^^I=12 %
}%
\newcommand\DifferentFilecontents{%
  \begingroup
  \@ifnextchar[\DifferentFilecontents@opt\DifferentFilecontents@noopt
}%
\newcommand\DifferentFilecontents@opt[2][]{%
  \DifferentFilecontentsVerbatimcatcodes
  \expandafter\@DifferentFilecontents\expandafter{\the\inputlineno}{[{#1}]}{#2}%
}%
\newcommand\DifferentFilecontents@noopt[1]{%
  \DifferentFilecontentsVerbatimcatcodes
  \expandafter\@DifferentFilecontents\expandafter{\the\inputlineno}{}{#1}%
}%
\newcommand\DifferentFilecontentsEqualMessage[2]{%
  \GenericWarning{%
    \space\space\space\@spaces\@spaces\@spaces\@spaces
  }{%
    LaTeX Information: %
    Seems the content of the existing file `#1'\MessageBreak
    equals the content of environment DifferentFilecontents\MessageBreak%
    on input lines #2 - \the\inputlineno.\MessageBreak
    The file will not be overwritten/will not be created anew\@gobble
  }%
}%
\newcommand\DifferentFilecontentsDifferentMessage[2]{%
  \GenericWarning{%
    \space\space\space\@spaces\@spaces\@spaces\@spaces
  }{%
    LaTeX Information: %
    Seems either the file `#1' does not exist\MessageBreak
    or its content does not equal the content of environment\MessageBreak
    DifferentFilecontents on input lines #2 - \the\inputlineno.\MessageBreak
    The file will be overwritten/created anew\@gobble%
  }%
}%

\newread\filecompareread

\begingroup
\catcode\endlinechar=12\relax%
\edef\delimitersbehind{\@backslashchar end\string{DifferentFilecontents\string}}%
\edef\filecontentsbegin{\@backslashchar begin\string{filecontents*\string}}%
\edef\filecontentsend{\@backslashchar end\string{filecontents*\string}}%
\newcommand\@DifferentFilecontents[4]{%
  \endgroup%
  \@ifdefinable\@DifferentFilecontents{%
    % ##1 - input-line-number of begin of environment
    % ##2 - optional arguments
    % ##3 - file name
    % ##4 - environment body
    % #1 = \end{readenvironmentbody}
    % #2 = ^^M
    % #3 = \begin{filecontents*}
    % #4 = \end{filecontents*}%
    \long\def\@DifferentFilecontents##1##2##3##4#2#1{%
      \immediate\openin\filecompareread=##3\relax%
      %\message{Environment-Body: (\detokenize{|##4|})}%
      \UD@CheckWhetherNull{##4}{%
        \DifferentFilecontentsCompareLoop{^^M}%
      }{%
        \expandafter\DifferentFilecontentsCompareLoop\expandafter{\@gobble##4^^M^^M}%
      }%
      {##2}{##3}{##4}{}{##1}%
      {%
         \immediate\closein\filecompareread\relax%
         \endgroup%
      }%
      \end{DifferentFilecontents}%
    }%
  }%
  \newcommand\DifferentFilecontentsCompareLoop[6]{%
    % ##1 - remainder of environment body
    % ##2 - optional arguments
    % ##3 - file name
    % ##4 - entire environment body
    % ##5 - remainder of current line of file
    % ##6 - input-line-number of begin of environment
    % #1 = \end{readenvironmentbody}
    % #2 = ^^M
    % #3 = \begin{filecontents*}
    % #4 = \end{filecontents*}%
    \UD@CheckWhetherNull{##5}{%
      %\message{1}%
      \ifeof\filecompareread\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi%
      {%
        %\message{1-1}%
        \UD@CheckWhetherNull{##1}{%
          %\message{1-1-1}%
          \Exchange{%
            \DifferentFilecontentsEqualMessage{##3}{##6}%
          }%
        }{%
          %\message{1-1-2}%
          \UD@CheckWhetherNull{##4}{%
            %\message{1-1-2-1}%
            %\message{Writing:(#3{##3}#2#4)}%
            \Exchange{%
              \DifferentFilecontentsDifferentMessage{##3}{##6}%
              \begingroup\newlinechar=\endlinechar\scantokens{\endgroup#3##2%
              {##3}#2#4}%
            }%
          }{%
            %\message{1-1-2-2}%
            %\message{Writing:(#3{##3}##4#2#4)}%
            \Exchange{%
              \DifferentFilecontentsDifferentMessage{##3}{##6}%
              \begingroup\newlinechar=\endlinechar\scantokens{\endgroup#3##2%
              {##3}##4#2#4}%
            }%
          }%
        }%
      }{%
        %\message{1-2}%
        \immediate\read\filecompareread to\templine%
        \expandafter\PassFirstToSecond\expandafter{\templine}{%
          \DifferentFilecontentsCompareLoop{##1}{##2}{##3}{##4}%
        }{##6}%
      }%
    }{%
      %\message{2}%
      \UD@CheckWhetherNull{##1}{%
        %\message{2-1}%
        \UD@CheckWhetherNull{##4}{%
          %\message{2-1-1}%
          %\message{Writing:(#3{##3}#2#4)}%
          \Exchange{%
            \DifferentFilecontentsDifferentMessage{##3}{##6}%
            \begingroup\newlinechar=\endlinechar\scantokens{\endgroup#3##2%
            {##3}#2#4}%
          }%
        }{%
          %\message{2-1-2}%
          %\message{Writing:(#3{##3}##4#2#4)}%
          \Exchange{%
            \DifferentFilecontentsDifferentMessage{##3}{##6}%
            \begingroup\newlinechar=\endlinechar\scantokens{\endgroup#3##2%
            {##3}##4#2#4}%
          }%
        }%
      }{%
        %\message{2-2}%
        %\edef\tempa{\romannumeral0\UD@ExtractFirstArgLoop{##5\UD@SelDOm}}%
        %\message{\detokenize\expandafter{\tempa}}%
        %\edef\tempb{\romannumeral0\UD@ExtractFirstArgLoop{##1\UD@SelDOm}}%
        %\message{\detokenize\expandafter{\tempb}}%
        \expandafter\UD@StringifySecond\expandafter\Exchange%
        \romannumeral0\UD@ExtractFirstArgLoop{##5\UD@SelDOm}{%
          \expandafter\UD@StringifySecond\expandafter\Exchange%
          \romannumeral0\UD@ExtractFirstArgLoop{##1\UD@SelDOm}{\ifx}%
        }%
        \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi%
        {%
          %\message{2-2-1}%
          \expandafter\PassFirstToSecond\expandafter{\@gobble##5}{%
            \expandafter\DifferentFilecontentsCompareLoop\expandafter{\@gobble##1}{##2}{##3}{##4}%
          }{##6}%
        }{%
          %\message{2-2-2}%
          \UD@CheckWhetherNull{##4}{%
            %\message{2-2-2-1}%
            %\message{Writing:(#3{##3}#2#4)}%
            \Exchange{%
              \DifferentFilecontentsDifferentMessage{##3}{##6}%
              \begingroup\newlinechar=\endlinechar\scantokens{\endgroup#3##2%
              {##3}#2#4}%
            }%
          }{%
            %\message{2-2-2-2}%
            %\message{Writing:(#3{##3}##4#2#4)}%
            \Exchange{%
              \DifferentFilecontentsDifferentMessage{##3}{##6}%
              \begingroup\newlinechar=\endlinechar\scantokens{\endgroup#3##2%
              {##3}##4#2#4}%
            }%
          }%
        }%
      }%
    }%
  }%
}%
\expandafter\PassFirstToSecond\expandafter{\filecontentsend}{%
  \expandafter\PassFirstToSecond\expandafter{\filecontentsbegin}{%
    \expandafter\PassFirstToSecond\expandafter{\delimitersbehind}{%
      \@DifferentFilecontents%
    }{^^M}%
  }%
}%


\makeatother



\begin{document}

%--------------------------------------------------------------------

\begin{DifferentFilecontents}%[overwrite]%
      {myfile.txt}
This is my file.

This is my file.
\end{DifferentFilecontents}

\section{The first variant of myfile.txt}
\IfFileExists{myfile.txt}{\input{myfile.txt}}{Does not exist!}

%--------------------------------------------------------------------

\begin{DifferentFilecontents}%[overwrite]%
      {myfile.txt}
This is my file.

This is my file.
\end{DifferentFilecontents}

\section{Once more the first variant of myfile.txt}
\IfFileExists{myfile.txt}{\input{myfile.txt}}{Does not exist!}

%--------------------------------------------------------------------

\begin{DifferentFilecontents}%[overwrite]%
      {myfile.txt}
This is my file - second variant.

This is my file - second variant.
\end{DifferentFilecontents}

\section{myfile.txt -- the second variant}
\IfFileExists{myfile.txt}{\input{myfile.txt}}{Does not exist!}

\end{document}

控制台输出:

$ pdflatex test.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.19 (TeX Live 2019/dev/Debian) (preloaded format=pdflatex).
entering extended mode
(./test.tex
LaTeX2e <2018-12-01>
(/usr/share/texlive/texmf-dist/tex/latex/base/article.cls
Document Class: article 2018/09/03 v1.4i Standard LaTeX document class
(/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo))
(/usr/share/texlive/texmf-dist/tex/latex/filecontents/filecontents.sty)
(./test.aux)

LaTeX Information: Seems either the file `myfile.txt' does not exist
                   or its content does not equal the content of environment
                   DifferentFilecontents on input lines 250 - 254.
                   The file will be overwritten/created anew.


LaTeX Warning: Overwriting file `./myfile.txt'.


(./myfile.txt)

LaTeX Information: Seems the content of the existing file `myfile.txt'
                   equals the content of environment DifferentFilecontents
                   on input lines 262 - 266.
                   The file will not be overwritten/will not be created anew.

(./myfile.txt)

LaTeX Information: Seems either the file `myfile.txt' does not exist
                   or its content does not equal the content of environment
                   DifferentFilecontents on input lines 274 - 278.
                   The file will be overwritten/created anew.


LaTeX Warning: Overwriting file `./myfile.txt'.


(./myfile.txt) [1{/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map}]
(./test.aux) )</usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmb
x12.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb
>
Output written on test.pdf (1 page, 25863 bytes).
Transcript written on test.log.

生成的.pdf 文件的图像:

在此处输入图片描述

如您在控制台输出中看到的,用于编译示例的是 LaTeX 2ε-release 2018-12-01,它比 LaTeX 2ε-release v1.3c,2019/09/11 更旧。filecontents*此旧版本的 -environment 不处理可选参数。因此,在示例中,可选参数被注释掉,并且包filecontents被加载,以将filecontents*-environment 变成不仅可以在序言中使用,还可以在 -environment 中使用的东西,document并且还会覆盖现有文件。

相关内容