11

I'd like to write some simple macros for handling things like cross-reference, to use them in plain TeX. (I know there are already macros devoted to this, like the ones present in Eplain, but I would like to try something different by myself.) So I need to know how to read from a file and how to write to a file. What are the TeX primitives that do such things? How do they work?

Another question: Can TeX 'call' other programs while it is running? I mean: Is there in TeX an equivalent to the system function present in the C language?

  • those are two questions. I suggest removing the later and putting it in its own question. – Lyndon White Dec 14 '14 at 14:56
  • 6
    Chapter 30 in TeX by Topic explains all details about writing on and reading from files. On TeX Live you access it with texdoc texbytopic. About the second question, look for shell-escape on the site. – egreg Dec 14 '14 at 14:58
  • 3
    if you want a use example, take a look at manmac.tex, the macros that knuth used to set the texbook (and other manuals). both cross-referencing and writing out of index terms are implemented there, and you can see in texbook.tex examples of the input used. (these are rather different from the latex approach, and indeed, from most other definitions i've seen. but obviously, they work effectively. both files are present in the tex live collection. – barbara beeton Dec 14 '14 at 16:41
  • I'm tempted to downvote: "no research effort". Have you read any manuals? – Martin Schröder Dec 22 '14 at 11:30
  • 1
    @MartinSchröder I started studying TeX with "The Joy of TeX", and then I moved onto "The TeXbook". By now I have not finished yet Knuth's book, but I have read a great part of it. And in what I read I found nothing about input/output (apart from the command \input), so I thought it isn't explained in it. But actually I was wrong. Next time, before asking a question, I'll consult more deeply my manuals :) – User Dec 23 '14 at 16:10
11

TeX has the \read and \write primitives for reading and writing to files plus of course \input for inputting an entire file 'here'. If you look at for example the LaTeX cross-ref mechanism is uses \write but avoids using \read (line-by-line) in favour of making use of \input with appropriately designed secondary files.

As \input is easy enough to understand, lets focus on \read and \write. Both of these work on a file stream, which is given a number but is usually allocated using \new.... For example

\newread\myread
\openin\myread=myinput %

\newwrite\mywrite
\immediate\openout\mywrite=myoutput %

will set up a read called \myread and a write called \mywrite. Notice that I've used \immediate with the \write: due to the asynchronous nature of the TeX page builder, you need to make sure that you ensure that \write operations happen in the 'correct' place. (More on this below.)

With two streams open we can for example write to the output. If we do two writes, one 'now' and one 'delayed'

\def\foo{a}
\immediate\write\mywrite{\foo}
\write\mywrite{\foo}
\def\foo{b}
Hello
\bye

the result is myoutput.tex reading

a
b

That's because \write\mywrite produces a whatsit that is only executed when a page is shipped out. That's useful if for example what you need to write contains a page number, as that is only known during the output stage. Also notice that \write acts like \edef: everything gets expanded unless you prevent it using \noexpand or a toks. Note, however, that this expansion is performed at the moment the \write operation is actually executed, so one must ensure macros have proper definitions when using a delayed \write.

The \read primitive reads one line at a time (unless braces are not matched) and tokenizes in the normal TeX way. You can arrange to loop over a file one line at a time using the \ifeof test on \myread, but as I say it's often easier to simply \input a file containing cross-refs.

If you want to do a system call, 'pure' TeX doesn't really help. However, Web2c has for a long time had a special 'stream' to allow escape to the system: \write18. This is a security risk and so as standard only a restricted set of commands are allowed in such an escape. You can do for example

pdftex --shell-escape myfile

to allow all escape: the risk if you've written all of the code yourself is only in making a mess-up! Doing a \write18 doesn't feed anything back to TeX: you'll need to arrange to read the result in some way, probably using \read on a secondary file.

As noted in a comment, an additional syntax extension available is \input|"<command>". This is again restricted by \write18 but does provide an expandable method to grab input from shell commands.

  • Did you forget about \input"|<command>"? – egreg Dec 14 '14 at 15:01
  • 2
    @egreg Availablity of that approach is rather less broad than for \write18, so depending on the requirement it may not be a possible. – Joseph Wright Dec 14 '14 at 15:05
6

Another special issue when dealing with cross references via external file is the order of reading, opening, writing and closing this file. The basic scheme is:

\newwrite\fileout  % allocations of the file number

\def...     ... all macros which can be used in the file must be defined
\input file ... input only when file exists (the file doesn't exist at the first run)
            ... this input stores the data from file into internal macros
\immediate\openout\fileout=file  ... this removes the data from file
                                  ... the file has zero length now

... document ... macros used in the document write (typically not immediately) 
                 data to the file

\end ... this closes the file automatically, 
         but if you need to use the data from the file at the end of the document
         (for example the TOC at the end of the document):
\vfil\eject  
\immediate\closeout\fileout
\input file ... this creates the TOC at the end of the file.
\end

The scheme above shows that you need to create a macro which reads the file by \input only when this file exists. You can use the following \softinput macro for such purposes:

\newread\testin
\def\softinput #1 {\let\next=\relax \openin\testin=#1
   \ifeof\testin \message{Warning: the file #1 does not exist}%
   \else \closein\testin \def\next{\input #1 }\fi
   \next}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.