4

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

  • 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 Feb 20 '18 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 Feb 20 '18 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 Feb 20 '18 at 14:09
  • 3
    The clue is in your title! use \gdef not \providecommand – David Carlisle Feb 20 '18 at 14:12
  • 2
    @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. – Marcel Krüger Feb 20 '18 at 14:26
11

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

  • 1
    That's brilliant! I've changed it a tiny bit in my answer to add error reporting, thanks again. – CharlieB Feb 20 '18 at 15:47
3

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.

  • 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 Feb 20 '18 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. – Marcel Krüger Feb 20 '18 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 Feb 20 '18 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 Feb 20 '18 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 Feb 20 '18 at 14:48
0

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}
  • siunitx is there purely to make sure it handles nested macros ok – CharlieB Feb 20 '18 at 15:48

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.