
Hier sind zwei Fragen zur filecontents
Umgebung:
ist es möglich, einer bereits vorhandenen Datei Inhalt hinzuzufügen oder zumindest die externe Datei geöffnet zu halten, um den Inhalt mehrerer
filecontents
Umgebungen an dieselbe externe Datei anzuhängen?ist es möglich herauszufinden, wie viele Zeilen von einer
filecontents
Umgebung in die externe Datei geschrieben wurden?
Antwort1
Das Zählen der Zeilen filecontents
ist einfach, da die Umgebung ihren Inhalt Zeile für Zeile verarbeitet, liest, prüft, ob die Umgebung beendet wurde, und schreibt dann. Sie müssen nur einen Zähler erstellen und ihn jedes Mal hinzufügen, wenn eine Zeile geschrieben wird.
Das Anhängen ist schwieriger. Erstens kann TeX (zumindest ohne Lua) keine Dateien mit der Berechtigung „Anhängen“ öffnen, also ist das ausgeschlossen. Die Idee, die Datei geöffnet zu lassen und später mit dem Schreiben fortzufahren, scheint zunächst einfacher, aber Sie müssten eine Reihe von Variablen in verschiedenen Umgebungen speichern, und dann könnten Sie nicht in die Datei schreiben a
, dann b
und dann an anhängen a
.
Im folgenden Code liest das \filecontents@preappend
Makro (wenn die append
Option verwendet wird) die Datei im Argument in ein temporäres Makro ein und \filecontents@append
fügt dann das gelesene Makro vor dem eigentlichen Inhalt der Umgebung in den Eingabestrom ein, sodass es genauso funktioniert, als ob Sie alles auf einmal eingegeben hätten. Die append
Option impliziert die Optionen overwrite
und noheader
.
Ich habe versucht, den Code der Kompaktheit halber als Patch zu schreiben, aber beim Patchen eines Befehls gehen anscheinend die ^^J
Zeichen verloren und der Header wird auf eine einzelne Zeile reduziert. Dies ist also eine vollständige Neudefinition von \filec@ntents
, aber die geänderten Zeilen sind markiert.
\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_eq:NN \FileSize \file_size:n
\ExplSyntaxOff
\makeatletter
\begingroup%
\@tempcnta=1
\loop
\catcode\@tempcnta=12 %
\advance\@tempcnta\@ne %
\ifnum\@tempcnta<32 %
\repeat %
\catcode`\^^M\active
\catcode`\^^L\active\let^^L\relax
\catcode`\^^I\active
\gdef\filec@ntents#1{%
\set@curr@file{\filec@ntents@checkdir#1}%
\edef\q@curr@file{\expandafter\quote@name\expandafter{\@curr@file}}%
\filecontents@preappend% <- ADDED for appending
\gaborit@reset@counter% <- ADDED for counting lines
\gaborit@reset@append% <- ADDED for counting lines
\openin\@inputcheck\q@curr@file \space %
\ifeof\@inputcheck%
\@latex@warning@no@line%
{Writing file `\@currdir\@curr@file'}%
\chardef\reserved@c15 %
\ch@ck7\reserved@c\write%
\immediate\openout\reserved@c\q@curr@file\relax%
\else%
\if@filesw%
\@latex@warning@no@line%
{File `\@curr@file' already \filec@ntents@where.\MessageBreak%
Not generating it from this source}%
\let\gaborit@step@counter\@empty% <- ADDED for counting lines
\let\gaborit@count@header\@empty% <- ADDED for counting lines
\let\write\@gobbletwo%
\let\closeout\@gobble%
\else%
\edef\reserved@a{#1}%
\edef\reserved@a{\detokenize\expandafter{\reserved@a}}%
\edef\reserved@b{\detokenize\expandafter{\jobname}}%
\ifx\reserved@a\reserved@b%
\@fileswtrue%
\else%
\edef\reserved@b{\reserved@b\detokenize{.tex}}%
\ifx\reserved@a\reserved@b
\@fileswtrue%
\fi%
\fi%
\chardef\reserved@c15 %
\ch@ck7\reserved@c\write%
\if@filesw% % Foul ... trying to overwrite \jobname!
\@latex@error{Trying to overwrite `\jobname.tex'}{You can't %
write to the file you a reading from!\MessageBreak%
Data is written to screen instead.}%
\else%
\@latex@warning@no@line%
{Writing or overwriting file `\@currdir\@curr@file'}%
\immediate\openout\reserved@c#1\relax%
\fi%
\fi%
\fi%
\closein\@inputcheck%
\if@tempswa%
\gaborit@count@header% <- ADDED for counting lines
\immediate\write\reserved@c{%
\@percentchar\@percentchar\space%
\expandafter\@gobble\string\LaTeX2e file `\@curr@file'^^J%
\@percentchar\@percentchar\space generated by the %
`\@currenvir' \expandafter\@gobblefour\string\newenvironment^^J%
\@percentchar\@percentchar\space from source `\jobname' on %
\number\year/\two@digits\month/\two@digits\day.^^J%
\@percentchar\@percentchar}%
\fi%
\let\do\@makeother\dospecials%
\count@ 128\relax%
\loop%
\catcode\count@ 11\relax%
\advance\count@ \@ne%
\ifnum\count@<\@cclvi%
\repeat%
\edef\E{\@backslashchar end\string{\@currenvir\string}}%
\edef\reserved@b{%
\def\noexpand\reserved@b%
####1\E####2\E####3\relax}%
\reserved@b{%
\ifx\gaborit@reset@append##1\relax% <- ADDED for counting lines
\gaborit@reset@append% <- ADDED for counting lines
\else% <- ADDED for counting lines
\ifx\relax##3\relax%
\immediate\write\reserved@c{##1}%
\gaborit@step@counter% <- ADDED for counting lines
\else%
\edef^^M{\noexpand\end{\@currenvir}}%
\ifx\relax##1\relax%
\else%
\@latex@warning{Writing text `##1' before %
\string\end{\@currenvir}\MessageBreak as last line of \@curr@file}%
\immediate\write\reserved@c{##1}%
\gaborit@step@counter% <- ADDED for counting lines
\fi%
\ifx\relax##2\relax%
\else%
\@latex@warning{%
Ignoring text `##2' after \string\end{\@currenvir}}%
\fi%
\fi%
\fi% <- ADDED for counting lines
^^M}%
\catcode`\^^L\active%
\let\L\@undefined%
\def^^L{\expandafter\ifx\csname L\endcsname\relax\fi ^^J^^J}%
\catcode`\^^I\active%
\let\I\@undefined%
\def^^I{\expandafter\ifx\csname I\endcsname\relax\fi\space}%
\catcode`\^^M\active%
\edef^^M##1^^M{%
\noexpand\reserved@b##1\E\E\relax}%
\filecontents@append}% <- ADDED for appending
%
% Code for append
\gdef\gaborit@pre@append{%
\begingroup%
\catcode`\^^L\active%
\catcode`\^^I\active%
\catcode`\^^M\active%
\let\do\@makeother\dospecials%
\count@ 128\relax%
\loop%
\catcode\count@ 11\relax%
\advance\count@ \@ne%
\ifnum\count@<\@cclvi%
\repeat%
\let^^M\relax%
\edef\gaborit@tmpa{\FileSize{\q@curr@file}}%
\ifnum\expandafter\@car\gaborit@tmpa\@nil=0\relax%
\endgroup \def\gaborit@append{^^M\gaborit@reset@append}%
\else%
\everyeof{\noexpand}%
\edef\gaborit@tmpa{\@@input\q@curr@file \space}%
\edef\x{\endgroup%
\edef\noexpand\gaborit@append{%
\noexpand\gaborit@trim@EOF\gaborit@tmpa\noexpand\gaborit@EOF}}\x%
\fi%
\filec@ntents@overwrite%
\filec@ntents@noheader}%
\gdef\gaborit@trim@EOF#1^^M\gaborit@EOF{^^M#1%
^^M\noexpand\gaborit@reset@append}%
\endgroup%
\def\filec@ntents@append{%
\let\filecontents@preappend\gaborit@pre@append
\def\filecontents@append{\gaborit@append}}
\let\filecontents@preappend\@empty
\let\filecontents@append\@empty
%
% For counting lines
\newcounter{FC@total@lines}
\newcounter{FC@lines}
\def\gaborit@reset@counter{\setcounter{FC@total@lines}{0}}
\def\gaborit@reset@append{\setcounter{FC@lines}{0}}
\def\gaborit@step@counter{%
\stepcounter{FC@total@lines}%
\stepcounter{FC@lines}}
\def\gaborit@count@header{%
\addtocounter{FC@total@lines}{4}%
\addtocounter{FC@lines}{4}} % Number of lines in the header
\makeatother
\begin{filecontents}[overwrite]{testfile.tex}
a
b
c
\end{filecontents}
\typeout{\arabic{FC@total@lines} lines written; \arabic{FC@lines} appended}
\begin{filecontents}[append]{testfile.tex}
d
e
f
g
\end{filecontents}
\typeout{\arabic{FC@total@lines} lines written; \arabic{FC@lines} appended}
\begin{document}
\end{document}
Wenn dieser Code ausgeführt wird, druckt TeX Folgendes auf dem Terminal aus:
LaTeX Warning: Writing or overwriting file `./testfile.tex'.
7 lines written; 7 appended
(./testfile.tex)
LaTeX Warning: Writing or overwriting file `./testfile.tex'.
11 lines written; 4 appended
(der FC@total@lines
enthält die Gesamtzahl der Zeilen in der Datei, und der FC@lines
Zähler enthält nur die Anzahl der angehängten Zeilen) und die Datei testfile.tex
enthält:
%% LaTeX2e file `testfile.tex'
%% generated by the `filecontents' environment
%% from source `test' on 2019/12/20.
%%
a
b
c
d
e
f
g
Wie Sie bemerkt haben, gilt in diesem Fall:
\begin{filecontents*}{testfile.tex}
\end{filecontents*}
%
\begin{filecontents}[append]{testfile.tex}
a
\end{filecontents}
(das heißt, schreiben Sie nichts und hängen Sie dann eine Zeile mit an a
). Die Datei, die Sie erhalten, ist:
a
Das liegt daran, dass TeX beim Lesen einer leeren Datei diese so behandelt, als handele es sich um eine Datei mit einer einzigen leeren Zeile 1 . Daher lässt sich zwischen diesen beiden Fällen nicht leicht unterscheiden und man muss das kleinere Übel wählen: entweder beim Anhängen an eine leere Datei eine unechte leere Zeile am Anfang der Datei haben oder beim Schreiben in eine Datei, die eine einzige leere Zeile enthält, eine leere Zeile entfernen lassen.
Mit den in pdfTeX eingeführten Dateidienstprogrammen (die aber heutzutage auch in anderen Engines verfügbar sind) können Sie jedoch die Größe (in Bytes) einer Datei abfragen, sodass Sie für eine leere Datei ein anderes Verhalten erzielen können. Im Code habe ich expl3
's verwendet \file_size:n
, um nicht mit nicht vorhandenen Dateien umgehen zu müssen.
1. Sie können dies mit dieser Testdatei sehen:
\catcode`\@=11
\newwrite\test
\def\testit#1{\immediate\openout\test \jobname.testfile\relax #1%
{\catcode`\^^M=13 \everyeof{\noexpand}%
\edef\tmpa{\ifdefined\@@input \@@input \else \input \fi \jobname.testfile }\show\tmpa}%
\immediate\closeout\test}%
\testit{}% empty file
\testit{\immediate\write\test{}}% single empty line
\csname stop\endcsname\bye
Dieses Verhalten liegt meiner Meinung nach daran, dass TeX \endlinechar
am Ende jeder Zeile ein hinzufügt. Wenn es eine leere Datei liest, liest es trotzdem eine Zeile (auch wenn sie leer ist) und fügt dann das \endlinechar
( ^^M
) ein, wodurch es so aussieht, als hätte die Datei eine einzelne leere Zeile.