53

For instance, I want to include a figure: ~/figures/figure.pdf. On my Linux box, this is /home/me/figures/figure.pdf, but on OS X it is /Users/me/figures/figure.pdf. I'd like it if there is an environment variable like $HOME so that the latex file can compile on both machines \includegraphics{$HOME/figures/figure.pdf}. (Of course it wouldn't use the $ sign).

I just found this post, but Is write18 the most native way? And, in the given example, the assignment wouldn't actually define the variable in LaTex. I tried

\immediate\write18{echo "\newcommand{\HOME}{$HOME}" > var.tex}

But gives me this error:

! Argument of \@gobble has an extra }.

So now I am at a loss.

hatmatrix
  • 2,913
  • 6
  • 31
  • 41
  • Related: http://tex.stackexchange.com/questions/1728/accessing-environment-variables-within-a-document – Dror Jan 19 '14 at 15:08

3 Answers3

44

If you have a recent TeX Live (2010 or later) or MiKTeX (v. 2.9), then the following works (and does not need the -shell-escape command line option):

\usepackage{catchfile}
\newcommand{\getenv}[2][]{%
  \CatchFileEdef{\temp}{"|kpsewhich --var-value #2"}{\endlinechar=-1}%
  \if\relax\detokenize{#1}\relax\temp\else\let#1\temp\fi}

\getenv[\HOME]{HOME}

If you only say \getenv{VAR} then the value of the variable is printed instead of being stored in a control sequence.

Not only HOME can be used, but any environment variable and also the "pseudovariables" defined in the TeX kpathsea system such as TEXMF or TEXINPUTS.

Note that this works only with pdflatex. With other engines or older distributions, shell escape is needed. Of course LuaTeX has its methods for interacting with the system.


A version that works with all recent engines is

\documentclass{article}

\usepackage{ifxetex,ifluatex}

\ifxetex
  \usepackage{catchfile}
  \newcommand\getenv[2][]{%
    \immediate\write18{kpsewhich --var-value #2 > \jobname.tmp}%
    \CatchFileDef{\temp}{\jobname.tmp}{\endlinechar=-1}%
    \if\relax\detokenize{#1}\relax\temp\else\let#1\temp\fi}
\else
  \ifluatex
    \newcommand\getenv[2][]{%
      \edef\temp{\directlua{tex.sprint(
        kpse.var_value("\luatexluaescapestring{#2}") or "" ) }}%
      \if\relax\detokenize{#1}\relax\temp\else\let#1\temp\fi}
  \else
    \usepackage{catchfile}
    \newcommand{\getenv}[2][]{%
      \CatchFileEdef{\temp}{"|kpsewhich --var-value #2"}{\endlinechar=-1}%
      \if\relax\detokenize{#1}\relax\temp\else\let#1\temp\fi}
  \fi
\fi

\begin{document}
\getenv[\HOME]{HOME}\show\HOME
\end{document}

In the case of xetex an auxiliary file \jobname.tmp is written and -shell-escape is necessary.

Note: the LuaTeX method has been suggested by Patrick Gundlach. If the variable is unset or not known to kpathsea, the empty string will result.


UPDATE 2019

With recent and up-to-date TeX distributions, the following works with every engine (except, of course, Knuth TeX): pdflatex, xelatex, lualatex, platex and uplatex. Unrestricted shell escape is not necessary.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\getenv}{om}
 {
  \sys_get_shell:nnN { kpsewhich ~ --var-value ~ #2 } { } \l_tmpa_tl
  \tl_trim_spaces:N \l_tmpa_tl
  \IfNoValueTF { #1 }
   {
    \tl_use:N \l_tmpa_tl
   }
   {
    \tl_set_eq:NN #1 \l_tmpa_tl
   }
 }

\ExplSyntaxOff

\begin{document}

\getenv[\HOME]{HOME}\show\HOME

\end{document}
egreg
  • 1,121,712
  • 2
    I'd like to use \getenv[VAR]{envvar} in a situation where envvar may not have been defined. I've been trying unsuccessfully to condition on the output of \getenv in this case. Is it possible to modify the command so that it returns something like UNDEFINED if there environment variable hasn't been defined? – Leo Simon Oct 24 '16 at 17:25
  • @LeoSimon Add before the \if\relax lines, the line \ifx\temp\empty\def\temp{UNDEFINED}\fi Or do a test for emptyness when the macro is used. – egreg Oct 24 '16 at 17:51
  • Thanks for the suggestion, but I can't get it to work. Here's an MWE, although I don't know how to put code into a comment \documentclass{article} \usepackage{catchfile} \newcommand{\getenv}[2][]{% \CatchFileEdef{\temp}{"|kpsewhich --var-value #2"}{}% \ifx\temp\empty\def\temp{UNDEFINED}\fi \if\relax\detokenize{#1}\relax\temp\else\let#1\temp\fi} \begin{document} The getenv command should return UNDEFINED but returns \getenv{NOT_DEFINED} \end{document} In this example \getenv{NOT_DEFINED} returns empty – Leo Simon Dec 04 '16 at 21:07
  • @LeoSimon If you have a followup question, please ask one – egreg Dec 05 '16 at 00:04
  • This used to work pretty well, but a recent change to kpsewhich broke it unfortunately: When called with --var-value, it doesn't print the environment variable's content verbatim, but does brace expansion on the value. Unfortunately, even that is buggy, so that it expands a comma even outside of curly braces:

    $ CHAMAELEOGEOMETRY='XX=yy,z=4' kpsewhich --var-value CHAMAELEOGEOMETRY XX=yy:z=4

    I used to suppl comma-separated values here (for setting a page geometry from outside), but since the comma gets converted to a colon, that doesn't any longer work. A kpsewhich bug?

    – Jan-benedict Glaw Dec 17 '18 at 16:20
  • One containing a comma, which gets "expanded" to a colon, thus breaking the actual variable content. – Jan-benedict Glaw Dec 17 '18 at 16:22
  • @Jan-BenedictGlaw What system are you on? On my system I get XX=yy,z=4 as output from the command line you show. – egreg Dec 17 '18 at 16:26
  • Then it was changed again.....

    I also found another workaround: Instead of using kpsewhich, a simple echo will do as well.

    – Jan-benedict Glaw Dec 17 '18 at 16:44
  • @Jan-BenedictGlaw The difference is that echo requires shell-escape, whereas kpsewhich doesn't. – egreg Dec 17 '18 at 16:45
  • The 'recent updates' in your last answer are very recent indeed. 2019-09-20? – steveo'america Nov 07 '19 at 22:18
  • @steveo'america I guess so. ;-) – egreg Nov 07 '19 at 22:33
  • I'm using the version in the first snippet here. I have an issue, if I type \getenv{HOME}. (with a dot after the end), in the output file a blank space is added between the content of HOME and .. Can somebody help? – Gilberto T. Dec 28 '19 at 16:34
  • @GilbertoT. I’ll have a look in s few days. – egreg Dec 28 '19 at 17:49
  • Thanks @egreg for the support. – Gilberto T. Dec 28 '19 at 20:41
  • 1
    @GilbertoT. \endlinechar=-1 was missing. – egreg Dec 30 '19 at 22:12
  • @egreg You're totally right. Thanks for the support. – Gilberto T. Dec 30 '19 at 22:26
15

@egreg has already answered your question so I thought I'd not answer it. That is suggest not relying on environment variables for this. If the latex file just goes \includegraphics{figures/figure.pdf}

then if $HOME/figures// is in TEXINPUTS environment variable or texmf.cnf configuration setting then the file will be found without LaTeX explicitly needing to access the machine-specific information. This keeps machine specific information and directory structure in the kpathsearch system which is optimised to deal with that rather than the macro layer which tries as far as possible to avoid such issues.

Also for the specific case of home directory you can (on web2c systems on at least linux and windows/cygwin, but I assume the others too) use \string~/figures/figure.pdf as kpathsea understands ~ as the home directory (but you have to pass it a literal ~ not the normal active definition for a non-breakable space, hence the \string.

David Carlisle
  • 757,742
3

The updated answer from @egreg for new versions did not work for me however this did:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\getenv}{om}
{
  \sys_get_shell:nnN{ kpsewhich ~ --var-value ~ #2 }{}#1
}

\ExplSyntaxOff

\begin{document}

\getenv[\HOME]{HOME}
\HOME

\end{document}

I do not know if this is the expected behaviour but it works for me and based on the documentation for \sys_get_shell:nnN it seems to be the intended behaviour.

Septatrix
  • 408
  • Not sure why you say “it doesn't work”, because your macro just does the same as mine, with the difference that you can't just say \getenv{FOO} to simply print the value. Oh, and you'll get some weird error if you don't specify the optional argument. – egreg Jun 03 '20 at 21:12
  • Oh I didn't even notice you specified the second parameter as optional. Your version leads to an error during compilation. Upon further inspection I got your version working by removing the \show – Septatrix Jun 03 '20 at 21:21
  • \show uses the error message mechanism. I used it just to print on the terminal the value assigned to \HOME. – egreg Jun 03 '20 at 21:26
  • Note that \usepackage{xstring} can be used to strip the extra space in the \HOME token with \StrGobbleRight{\HOME}{1}[\strippedHOME] – jlettvin Nov 09 '23 at 18:37