8

I'm writing a long document and, to make my life easier, I like to define quantites I use as macros so that I just have to update them in one place when they change. For me, it makes sense to define these as I go along, in the section of my document which is most relevent to them. However, sometimes I'd like to use these commands in other chapters, sometimes even in previous chapters.

I'd therefore like to add a macro which spits a definition of a command into the latex .aux files, so that a command can be used everywhere once it's been defined. So far I've got this:

% Make a command which defines a macro with \providecommand but in the aux file,
% so it's accessible to all other chapters
\makeatletter
\newcommand{\doccommand}[2]{%
\protected@write\@auxout{}{\protect\providecommand\protect#1{#2}}%
}
\makeatother

This puts the following into the aux file

\providecommand \tester {123}

for a chapter like this:

\chapter{Test chapter}

This is a test chapter!

Tester is \tester{}.

\doccommand{\tester}{123}

Unfortunately, it doesn't work. Even using the command within the test chapter gives me "Undefined control sequence", and the same in other chapters.

Originally I thought this might have something to do with the spaces that ended up in the .aux file, but I manually removed them and compiled with an \includeonly{a_different_chapter} which still gives the same error.

What gives?

Update:

Thanks to all the help here, I got this working and wrote it into a little package. If you're interested, you can find it on CTAN at https://ctan.org/pkg/globalvals

7
  • 2
    Have you thought about placing all of your macros in a separate file (with extension .tex) and to \input this file in the preamble? That way, all of your macros will automatically be accessible globally.
    – Mico
    Commented Feb 20, 2018 at 14:06
  • 2
    I have, and I actually already do that for lots of macros. However I really like the idea of defining quantities right next to where I write about them. For example, I might say Foo was measured and its value was found to be \fooValue{} where \foovalue is \SI{100(10)}{\meter}. I have an awful lot of measurements like this, so piling them all into an external text file is possible, but I'd prefer to do it this way.
    – CharlieB
    Commented Feb 20, 2018 at 14:08
  • PS I forgot to mention in the question, but I need the commands not to expand so that defintions like the above are possible
    – CharlieB
    Commented Feb 20, 2018 at 14:09
  • 3
    The clue is in your title! use \gdef not \providecommand Commented Feb 20, 2018 at 14:12
  • 3
    @CharlieB \providecommand doesn't work because the aux-file is always read in a group. This group ends after reading the aux-file, so local definitions in the aux-file are never visible from the document. Commented Feb 20, 2018 at 14:26

3 Answers 3

15

I'd use a different strategy: use a wrapper command, rather than directly defining macros.

\documentclass{article}

\makeatletter
\newcommand{\usevalue}[1]{%
  \ifcsname usevalue@#1\endcsname
    \csname usevalue@#1\endcsname
  \else
    ??%
  \fi
}
\newcommand{\definevalue}[2]{%
  \write\@auxout{%
    \unexpanded{\global\@namedef{usevalue@#1}{#2}}%
  }
}
\makeatother

\begin{document}

Something with \usevalue{tester}.

Something else.

Now we can define \texttt{tester} and use again it: \usevalue{tester}.

\definevalue{tester}{42}

\end{document}

Output of first run

enter image description here

Output of second run

enter image description here

6
  • 1
    That's brilliant! I've changed it a tiny bit in my answer to add error reporting, thanks again.
    – CharlieB
    Commented Feb 20, 2018 at 15:47
  • @egreg Is it possible to have expand wrapper command name? Please have a look at related question I asked here: tex.stackexchange.com/q/520174/192717
    – codepoet
    Commented Dec 12, 2019 at 12:49
  • Brilliant indeed: so simple and efficient! Exactly what I was looking for: Thanks! Commented Mar 16, 2022 at 17:11
  • How does one proceed if the second argument to \definevalue is another command? Possibly non-expandable? I'm unable to figure out the required \expandafter mayhem.
    – Steven
    Commented Aug 2 at 11:11
  • @Steven Probably it's better if you ask a specific question with the details.
    – egreg
    Commented Aug 2 at 14:41
4

You could write

\makeatletter
\newcommand{\doccommand}[2]{%
  \immediate\write\@auxout{\gdef\noexpand#1{\unexpanded{#2}}}%
  \gdef#1{#2}%
}
\makeatother

This will do a global define without expanding the replacement text. I added an additional direct \gdef so that the command can be used without rerunning TeX.

But this is not a good idea: If you never use the command before the point where it is defined, defining it in the aux-file is useless. If you use the command before defining it, LaTeX never reaches the point where you write the aux-file entry.

So you can only use the command after running TeX once with the command defined but not used. If you ever delete the aux-file, your document is broken. If you ever only include a different chapter, the aux-file entry will not be written, so your document is broken.

Instead you could create a separate file with the definitions which you include in the preample. It's more work, but it results in a much more stable document.

7
  • Thanks! In my version of Latex at least, errors are skipped over and reported at the end of the document, so a missing aux file wouldn't be a game ender. I understand that it's probably bad practise to intentionally introduce errors into the build process, but is this so bad in this case?
    – CharlieB
    Commented Feb 20, 2018 at 14:26
  • @CharlieB Even if your LaTeX is configured to report errors at the end this introduces a dependency on your current setup. If you ever want to change your LaTeX environment this might break and then you might not remember what was causing it. Debugging this will be a nightmare. Commented Feb 20, 2018 at 14:45
  • Hmm true. I suppose the proper way of doing this is to create a package that provides \gcmd{fooValue} and throws a warning when it's not defined instead of an error. OK, thanks for your advice!
    – CharlieB
    Commented Feb 20, 2018 at 14:47
  • There's another point to underline: \gdef will silently overwrite existing commands. On the other hand, the proposed \providecommand would fail if the command is already defined.
    – egreg
    Commented Feb 20, 2018 at 14:47
  • @egreg Yep, an error for multiple definitions would be good. Don't aux files get included more than once though, so \gdef is needed here?
    – CharlieB
    Commented Feb 20, 2018 at 14:48
1

Thanks @egreg for a clever approach. I've altered his code very slightly to add an error message if two definitions are made in the same document:

\documentclass{article}    

\usepackage{siunitx}

\makeatletter
\newcommand{\useVal}[1]{%
  \ifcsname useVal@#1\endcsname
    \csname useVal@#1\endcsname
  \else
    ??%
  \fi
}
\newcommand{\defVal}[2]{%
\ifcsname useVal@#1@defined\endcsname
    \PackageError{useVal}{Value "#1" already defined}{}
\else
  \write\@auxout{%
    \unexpanded{\global\@namedef{useVal@#1}{#2}}%
  }
  \global\@namedef{useVal@#1@defined}{}
\fi
}
\makeatother

\begin{document}

Testval is \useVal{testVal}.

Now defining testval...

\defVal{testVal}{\SI{123}{\meter}}

Now it's \useVal{testVal}. 

% This would throw an error:
% \defVal{testVal}{Not this please!}

\end{document}
1
  • siunitx is there purely to make sure it handles nested macros ok
    – CharlieB
    Commented Feb 20, 2018 at 15:48

You must log in to answer this question.

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