6

I have an issue with the \input command.

As it is stated here, "it's equivalent to typing all the commands from filename.tex right into the current file where the \input line is."

I have the feeling that this is not exactly the case, at least in some situations, and I would like some clarifications.

Consider this MVCE (say, in a file main.tex):

\documentclass{article}
\usepackage{mypackage}
\begin{document}
\begin{myenvir}{Hello}
\mycommand{World}
\end{myenvir}
\end{document}

Now the mypackage.sty file (stored in same folder):

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{mypackage}[version 0.1]

\newenvironment{myenvir}[1]
{%
    \newcommand{\mycommand}[1]
    { test:command arg=##1 environment arg=#1}
    \begin{center}
}
{\end{center}} 
\endinput

When compiling main.tex, this works fine, and produces.

test:command arg=World environment arg=Hello

But now, lets say for some reason (*), I want to store the command definition in a separate file. So I just change the sty file to:

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{mypackage}[version 0.1]

\newenvironment{myenvir}[1]
{%
    \input{fcommand}
    \begin{center}
}
{\end{center}} 
\endinput

And store into a file fcommand.tex these two lines:

\newcommand{\mycommand}[1]
{ test:command arg=##1 environment arg=#1}

Shouldn't that give strictly the same result?

But when compiling main.tex I get this error:

You can't use `macro parameter character # in horizontal mode

and the output (pdf) file holds:

test:command arg=1 environment arg=World

Question: what is going on ? A far as I understand, there seems to be a problem with argument substitution when code is in a separate file. Thus my assumption that \input is not truly equivalent to having the code in same file.

I have also checked this related question, where the answer states to add \unskip. So I tried this:

\input{fcommand}\unskip%

and changed the command file content:

\newcommand{\mycommand}[1]
{ test:command arg=##1 environment arg=#1}\endinput

But still got the same error.

Other related question: https://tex.stackexchange.com/a/319512/11083

(*) On the reason to try this, this question is actually a follow up on another question I am currently dealing with.

kebs
  • 859
  • did you see the answer from Martin Scharrer and especially the 2nd comment. – albert Nov 03 '18 at 18:00
  • By the way, this minimal document replicates your behaviour. For future reference, rather than saying I have file1.tex that looks like this, and file2.tex that looks like this, and I use them in main.tex in this way, try to provide a single, contained copy-and-paste example. I know it may not always be possible. – Werner Nov 03 '18 at 18:02
  • The issue here is that you're after an environment argument that \mycommandA knows nothing about. Is your main question that you want to use the argument of the environment (if there was one) in a command that you define inside the environment? – Werner Nov 03 '18 at 18:04
  • @Werner Yes, I understand you concern (not easy to get through the code when spread in the text). But I wasn't aware of the package filecontent. And given the case (several files involved), I couldn't really do it another way. – kebs Nov 03 '18 at 18:07
  • @albert Sorry, I have 4 linked questions, with multiple answers ;-) ! You mean the second one here ? About not using \include? Not sure it is related here... – kebs Nov 03 '18 at 18:11
  • No I meant the first (https://tex.stackexchange.com/questions/246/when-should-i-use-input-vs-include/250#250) – albert Nov 03 '18 at 18:12
  • The answer of Martin Scharrer is just below it, I should have linked to the question not to the reference you gave, – albert Nov 03 '18 at 18:34
  • @albert I read all the answers and all the comments on that page, sorry, I still don't see what you are trying to tell me. Maybe you could tell me what your point is? – kebs Nov 03 '18 at 18:42
  • @kebs: You didn't answer my question in the comment. Are you after a command that has access to the environment argument? If so, there are ways around using ##1 and #1 the way you do. – Werner Nov 03 '18 at 19:20
  • @Werner Sorry, yes, I do need to have access to the environment argument (but I assumed this was clear from the code: test:command arg=##1 environment arg=#1 ?) Or maybe I don't understand what you question is ? What do you mean by "there are ways around using ..." ? – kebs Nov 03 '18 at 23:45
  • 2
    by the way the answer to your question in bold is essentially "no". The replacement text of a definition is not interpreted at all at the point of definition so there is essentially no \input there. It is interpreted at the point the definition is used, and there it does give the same error as if you have used the ## forms inline. – David Carlisle Nov 04 '18 at 11:55
  • Thanks @David, that is also the conclusion I have: in such a case \input'ing a file is not strictly equivalent to having the code "inside". – kebs Nov 04 '18 at 12:53
  • 1
    @kebs there is only an \input when the command is used. Not at the point of definition, and if you use ## at that point then you get the same error, so i would say that they are equivalent. Nothing special about input here, you can have any command \foobar in the definition and it does not even need to be defined, \foobar will only give an error if you use the environment and it is not defined at that point. – David Carlisle Nov 04 '18 at 13:00
  • @DavidCarlisle this is not clear, the question is why it works without \input but it doesn't with it? – touhami Nov 04 '18 at 13:27
  • @touhami I'm not sure how else I can say it. I struggle to answer as I would never expect the ## in the input file to work as the OP intended. But then I'm reading it as tex reads it, presumably kebs is reading it with a different expectation... – David Carlisle Nov 04 '18 at 13:28
  • Correct, I assumed it worked just like the #include in C or C++ language, but it appears it is not the same (although maybe you can also have some oddities with #include? can't be sure...) – kebs Nov 05 '18 at 10:56

1 Answers1

6

The problem is that when TeX reads the environment command it detects where all the #1 are, but the \input is not processed. When the environment is executed (that is, the underlying \myenvir command is expanded), all the #1 seen previously are replaced by the argument Hello. So using

\begin{myenvir}{Hello}
\mycommand{World}
\end{myenvir}

is the same as (minus the comments)

% \begin{myenvir}{Hello}
\input{fcommand}
\begin{center}
% environment contents
\mycommand{World}
% \end{myenvir}
\end{center}

The Hello was lost because it wasn't used anywhere. Now the \input is expanded:

% \begin{myenvir}{Hello}
\newcommand{\mycommand}[1]
{ test:command arg=##1 environment arg=#1}
\begin{center}
% environment contents
\mycommand{World}
% \end{myenvir}
\end{center}

Then, after the definition is done, the expansion of \mycommand yields:

% \begin{myenvir}{Hello}
% \newcommand{\mycommand}[1] %%% Already defined
% { test:command arg=##1 environment arg=#1}
\begin{center}
% environment contents
test:command arg=#1 environment arg=World
% \end{myenvir}
\end{center}

Ans now you're trying to write #1 in the middle of the text, which raises the error:

! You can't use `macro parameter character #' in horizontal mode.
l.40 test:command arg=#
                       1 environment arg=World
  • Thanks for answer! I'll need some time to get it all. You have any idea how I can solve the problem (i.e. make the trick work)? – kebs Nov 03 '18 at 18:14
  • 1
    @kebs Sorry, I tried, but I couldn't come up with anything clever. The only solution I can think about is to store the argument of the environment in a macro, and use that macro in the definition of \mycommand. Like this: https://pastebin.com/2c8DQNt7 – Phelype Oleinik Nov 03 '18 at 18:50
  • 1
    @kebs just save the argument while it is in scope, if your environment does \def\zzz{#1} before the \inut then the commans defined in that file can use \zzz to get access to that argument, Hello in the example. – David Carlisle Nov 04 '18 at 10:57
  • @DavidCarlisle and Phelype, Thanks to both, I think you both describe the same workaround, I'll try that. – kebs Nov 04 '18 at 11:49