我们知道\IfFileExists{<file>}{<yes>}{<no>}
。
如何\IfFileHasChanged{<file>}{<yes>}{<no>}
为已更改(并且确实存在!)的文件创建命令。
我的意思是:
如果有的
myfile.txt
话This is my file.
应该写在第一个(带filecontents
)。如果我改了
myfile.txt
,Now 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
上次运行后是否发生变化来执行不同的处理。
我的解决方案:
它将在
.aux
文件(在运行开始时自动读取)上写入文件的 MD5 校验和的值myfile.txt
;它将检查自上次运行以来是否发生了变化,如果是或否,则执行不同的操作;
它需要一个相当新的 LaTeX 发行版(不知道什么时候
\file_get_mdfive_hash:nN
推出的)这是我的第一个 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
并且还会覆盖现有文件。