63

I would like to include part of the contents of a .tex file into a different .tex file. I don't have the option of modifying the file to include. The file to include may change so simply copying and pasting the part I need is not ideal.

Essentially, I'm wondering if there is something that would work like

\input[start-end]{file.tex}

where start and end are the first and last lines to include from the file?

6
  • 4
    If the file will change, wouldn't some kind of start/end tags be safer than specifying line numbers?
    – Seamus
    Nov 3, 2010 at 21:18
  • 8
    An alternative to a TeX solution might be to add some sed-fu to your makefile. pipe the output of sed -n 'x,yp file to a temporary file and input that. (where x and y are start and end lines respectively)
    – Seamus
    Nov 3, 2010 at 21:23
  • (Or something like that. I just looked up how to do this and I'm not sure I'd be able to do it myself...)
    – Seamus
    Nov 3, 2010 at 21:26
  • 7
    sed -ne 42,99p will copy lines 42–99 (inclusive) of its input to its output. Nov 3, 2010 at 22:00
  • @Harald, could you add your comment as an answer? Nov 3, 2010 at 22:37

4 Answers 4

26

Here is an approach in pure TeX (well, e-TeX). The main idea is: to select a range of lines in a file, sed is overkill, TeX is more than enough. As a bonus, no temporary file is needed.

\documentclass[a4paper]{article}
\usepackage[ascii]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}

\makeatletter
\newread\pin@file
\newcounter{pinlineno}
\newcommand\pin@accu{}
\newcommand\pin@ext{pintmp}
% inputs #3, selecting only lines #1 to #2 (inclusive)
\newcommand*\partialinput [3] {%
  \IfFileExists{#3}{%
    \openin\pin@file #3
    % skip lines 1 to #1 (exclusive)
    \setcounter{pinlineno}{1}
    \@whilenum\value{pinlineno}<#1 \do{%
      \read\pin@file to\pin@line
      \stepcounter{pinlineno}%
    }
    % prepare reading lines #1 to #2 inclusive
    \addtocounter{pinlineno}{-1}
    \let\pin@accu\empty
    \begingroup
    \endlinechar\newlinechar
    \@whilenum\value{pinlineno}<#2 \do{%
      % use safe catcodes provided by e-TeX's \readline
      \readline\pin@file to\pin@line
      \edef\pin@accu{\pin@accu\pin@line}%
      \stepcounter{pinlineno}%
    }
    \closein\pin@file
    \expandafter\endgroup
    \scantokens\expandafter{\pin@accu}%
  }{%
    \errmessage{File `#3' doesn't exist!}%
  }%
}
\makeatother

% for testing purpose
\begin{filecontents}{pitest}
5 You shouldn't see this.\par
6 Let the fun begin!
7 \iffalse
8 bla
9 \fi

11 New paragraph.
12 \verb*+\foo (two  spaces)~_$^+ bla
13 You shouldn't see this.\par
\end{filecontents}

\begin{document}

\partialinput{6}{12}{pitest} random text

\end{document}

I think/hope this approach is robust, mostly thanks to e-TeX's \readline. See the included test file for potential edge cases that do work.

6
  • Is performance going to be an issue for long excerpts: the \edef\pin@accu{\pin@accu\pin@line} is going to repeatedly expand the same text, and looks like it will cost O(n^2)? Nov 5, 2010 at 13:37
  • 2
    Yes, performance may be an issue. I don't think it's possible to do list kind of list operation in less than O(n²) with TeX. For long excerpts, it may be better to use a temporary file rather than a macro: open it before the second loop, write to it instead of adding to \pin@accu, close it and \input it after the loop. As a bonus, you don't need to change \newlinechar so the group becomes useless. But don't forget to use \immediate on all file operations!
    – mpg
    Nov 5, 2010 at 16:19
  • Performance is not such an issue for me, and a pure-TeX solution is preferable, so this is what I need - thanks!
    – astrofrog
    Nov 15, 2010 at 14:19
  • 1
    To improve the performance you could change \edef\pin@accu{\pin@accu\pin@line} to \expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\pin@accu\expandafter\expandafter\expandafter{\expandafter\pin@accu\pin@line} so that both macros are only expanded once. Using a token register (\newtoks\pin@accu) would be even better: \pin@accu\expandafter\expandafter\expandafter{\expandafter\the\expandafter\pin@accu\pin@line}. Apr 6, 2011 at 12:17
  • 1
    @mpg I get some strange results using this. If I write some text before using \partialinput, there is a horizontal space between that word and whatever is on the line(s) fetched with \partialinput. How is that solved
    – Hugo
    May 10, 2013 at 13:58
77

Manuel has posted an answer that uses line numbers; there's a recent package on CTAN that lets you do it with tags in the input file: catchfilebetweentags.

An example is

\usepackage{catchfilebetweentags}
...
\ExecuteMetaData[file.tex]{tag}

where the contents of the external file is surrounded by the "tags":

%<*tag>
...
%</tag>
7
  • 5
    I upvoted your answer because I didn't know this package and it sounds very useful, but, just nitpicking: it doesn't solve the OP's problem proper, since he mentioned he can't change the external file. But I agree that tags are more robust in general.
    – mpg
    Nov 5, 2010 at 12:41
  • 1
    Yes, I agree your answer is better! I just added this answer for completeness. Nov 5, 2010 at 22:05
  • This is very cool, I will definitely look into this package some more for other projects.
    – astrofrog
    Nov 15, 2010 at 14:20
  • 1
    This is really great - I'm building a poster and with this it is trivial to produce an alternative layout and be able to edit the content separately.
    – AnjoMan
    Sep 17, 2014 at 14:33
  • 1
    This is amazing. I can't believe that I've never heard of this package. Dec 8, 2017 at 6:11
8

As is stated in the comments, sed -ne 42,99p will copy lines 42–99 (inclusive) from its input to its output. That is assuming you're on some sort of unix (though I suppose you can have sed on windows too). You can do this in a script or a makefile, putting the result in a temporary file that you include.

Alternatively, assuming you are on unix, you can resort to some mild unix wizardry: Say the TeX file you want to include part of, is named foo.tex. Then you can do

mkfifo foosnips.tex
while sed -ne 42,99p foo.tex > foosnips.tex || [ $? == 141 ]; do sleep 1; done &

(modify for your shell if it's not (ba)sh) and then input foosnips in your master TeX file. Each time you TeX it, the sed command will extract the requested lines. The background job should keep on running until you reboot the computer, but thanks to the way fifos work, it will not actually do anything until the fifo is opened for reading. Kill it by hand when it is no longer needed, or if you need to adjust the parameters.

Doing it that way is perhaps not worth it, unless foo.tex changes very often, or the extract is very large and you don't want to keep it as a separate file.

(The test for exit status 141 (128+13, where 13 is the signal number of the PIPE signal) is there to make sure the loop is not terminated if you read only part of the output from the fifo, as when TeX is quit due to some error. And the sleep is needed to make sure the process at the receiving end of the fifo has a chance to close the file before a new sed process starts.)

2
  • Could this be modified to read particular start/end tags in the file? I ask because I personally think using line numbers to select the text seems a bad idea. (Although for a specific context, I guess it could be fine...)
    – Seamus
    Nov 4, 2010 at 13:33
  • 2
    @Seamus: Sure, just replace 42,99p by '/start/,/end/p'. (The quotes are only needed if the text start or end is replaced by something containing spaces or other characters special to the shell.) Beware that these texts are interpreted as regular expressions, though, so square brackets, asterisks and question marks in particular are special. This prints lines between occorrences of start and end inclusive, with repetitions if several such pairs exist. One alternative is sed -ne '/end/q;/start/,$p' which does not repeat (and skips the end line). Sed syntax is weird and powerful. Nov 4, 2010 at 14:16
2

Here's something that's totally untested!

\newread\pin
\newcount\pcount
\def\partialinput[#1-#2]#3{%
    \openin\pin #3
    \global\pcount0
    \loop\ifnum\pcount<#1\relax
        \read\pin to\l
        \global\advance\pcount1
    \repeat
    \loop\ifnum\pcount<#2\relax
        \read\pin to\l\l
        \global\advance\pcount1
    \repeat
    \read\pin to\l\l
    \closein\pin
}

Modulo errors, that defines a new macro \partialinput that should work as you describe.

Actually, that numbers lines starting at 0. replace the \global\pcount0 with \global\pcount1 to start at 1 instead.

3
  • 2
    I don't think \read\pin to\l\l is going to be robust: what happens if the next line is something like \ifx\cs\relax? Nov 4, 2010 at 9:35
  • Additionnaly, the catcodes are fixed when the line is read, so this approach will break if the somthing in the file tries to change them.
    – mpg
    Nov 4, 2010 at 14:18
  • @Charles, @mpg: Yes, both are true. I can't imagine reading specific ranges of files is going to be particularly robust anyway, so I didn't bother going the e-TeX route with \readline and \scantokens, but as mpg demonstrates, it's not really that hard to do.
    – TH.
    Nov 5, 2010 at 1:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .