假设我使用以下命令编译以下文件pdflatex -shell-escape test.tex
:
\documentclass{minimal}
\begin{document}
File listing is:
\immediate\write18{ls /usr}
\end{document}
这会将命令的输出发送ls /usr
到报告/日志pdflatex
(主要发送到stdout
)。
我想利用两种情况:
ls /usr
直接包含在文档中的输出(LaTeX 流)。- 的输出
ls /usr
成为的内容\newcommand
(我的意思是,我希望在\newcommand
第一次执行时执行脚本 - 并且在随后调用新命令时,shell 代码不应重新执行)。
我已经读完了如何从 LaTeX 执行 shell 脚本?,但我不确定这种“管道输入”是否可以应用于\newcommand
。
我也读过tex - 如何将 shell 输出保存到 LaTeX 中的变量?- 并且似乎应该使用 Tex 的文件 I/O;但我很不喜欢这样一个事实,即我仍然必须将脚本输出(实际上,在本例中是输出ls /usr
)重定向到文件,然后读入它,将其作为命令的内容。
那么,有没有更简单的方法来实现我想要的(希望通过基于上述代码的示例来说明)?
编辑:嗯,我应该早点问一个问题:)我会尝试在这里进行编辑,尽管它可能会被忽略.. :)
我最初要求一个\newcommand
仅在其定义时执行 shell 代码(即,在某种意义上它是“缓存的”);而 @egreg 的答案正是如此。但是 - 是否有可能有一个不同的\newcommand
定义,这样每次调用这个新命令时,shell 命令都会重新执行?即执行类似的东西\@@input|"cat tempfile"
(不能这样执行),tempfile
调用之间会发生变化?
答案1
\documentclass{article}
\begingroup\makeatletter\endlinechar=\m@ne\everyeof{\noexpand}
\edef\x{\endgroup\def\noexpand\TeXpath{\@@input|"which tex" }}\x
\begin{document}
File listing is
{\catcode`_=12 \ttfamily
\input{|"ls /usr" }
}
\TeX{} is \TeXpath
\end{document}
我们必须使用\@@input
(原始\input
命令),因为\input
在 LaTeX 中会进行赋值。的设置\endlinechar
是为了避免在扩展中出现虚假空格\TeXpath
。
当 shell 转义处于活动状态并且原语\input
找到 时|
,它会接受以下 shell 命令的标准输出作为输入。
H. Oberdiek 应该有一个包可以做这种事。
笔记赋值是任何为控制序列或寄存器赋予含义或值的 TeX 操作。在\edef
操作过程中,TeX 会扩展括号内找到的所有命令,直到只剩下不可扩展的标记,但不执行任何任务;相反,类似\catch=22
(其中是计数寄存器的名称)的内容仍然完全不变。由于LaTeX 中\catch
的定义是\input
\@ifnextchar\bgroup\@iinput\@@input
执行的隐式赋值\@ifnextchar
将不会执行,并且两个都 \@input
和\@@input
会被展开,这会导致彻底的灾难。相反,\input
原语(LaTeX 保存为\@@input
)是可扩展的,其扩展包括使 TeX 读取命名文件。当然,必须小心这个文件包含的内容,因为这也会被展开。因此,在执行此类操作时必须采取其他预防措施,具体取决于我们要执行的命令产生的标记的性质,并且此“解决方案”只是可能的“真实”应用程序的框架。
2019 年更新
几年后,情况发生了变化,并且出现了更好的方法。
例如,可以使用xparse
并expl3
改进代码:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\captureshell}{som}
{
\sdaau_captureshell:Ne \l__sdaau_captureshell_out_tl { #3 }
\IfBooleanT { #1 }
{% we may need to stringify the result
\tl_set:Nx \l__sdaau_captureshell_out_tl
{ \tl_to_str:N \l__sdaau_captureshell_out_tl }
}
\IfNoValueTF { #2 }
{
\tl_use:N \l__sdaau_captureshell_out_tl
}
{
\tl_set_eq:NN #2 \l__sdaau_captureshell_out_tl
}
}
\tl_new:N \l__sdaau_captureshell_out_tl
\cs_new_protected:Nn \sdaau_captureshell:Nn
{
\sys_get_shell:nnN { #2 } { } #1
\tl_trim_spaces:N #1 % remove leading and trailing spaces
}
\cs_generate_variant:Nn \sdaau_captureshell:Nn { Ne }
\ExplSyntaxOff
\begin{document}
\captureshell*[\TeXpath]{which tex} % we need to stringify it because of _
File listing is
{\ttfamily\captureshell{ls \jobname.*}\par}
\TeX{} is \texttt{\TeXpath}
\end{document}
如果用户没有通过-shell-escape
LaTeX 运行选项,我们可以添加一条错误消息。
也检查texosquery
(需要 Java)。
答案2
这是一个简单的方法,使用我的bashful
包
\documentclass{article}
\usepackage[a6paper]{geometry}
\usepackage{bashful}
\begin{document}
\bash[script,stdout]
ls -F /usr
\END
\end{document}
从而产生
答案3
我认为这个片段对这个主题会有帮助。
https://gist.github.com/w495/7328b76e76aee49657e0bd7a3b46c870
% !TeX encoding = UTF-8
\ProvidesPackage{bashline}[2016/10/24 v. 0.1]
\makeatletter
\newcommand{\bashline@file@name}[1]{%
/tmp/${USER}-${HOSTNAME}-\jobname-#1.tex%
}
\newread\bashline@file
\newcommand{\bashline@command@one}[2][tmp]{%
\immediate\write18{#2 > \bashline@file@name{#1}}
\openin\bashline@file=\bashline@file@name{#1}
% The group localizes the change to \endlinechar
\bgroup
\endlinechar=-1
\read\bashline@file to \localline
% Since everything in the group is local,
% we have to explicitly make the assignment global
\global\let\bashline@result\localline
\egroup
\closein\bashline@file
% Clean up after ourselves
\immediate\write18{rm \bashline@file@name{#1}}
\bashline@result
}
\newcommand{\bashline@command@many}[2][tmp]{%
\immediate\write18{#2 > \bashline@file@name{#1}}
\openin\bashline@file=\bashline@file@name{#1}
% The group localizes the change to \endlinechar
\newcount\linecnt
\bgroup
\endlinechar=-1
\loop\unless\ifeof\bashline@file
\read\bashline@file to \localline%
\localline
\newline
\repeat
\egroup
\closein\bashline@file
% Clean up after ourselves
\immediate\write18{rm \bashline@file@name{#1}}
}
\newcommand{\bashline}[2][tmp]{%
\bashline@command@one[#1]{#2}%
}
\newcommand{\bashlines}[2][tmp]{%
\bashline@command@many[#1]{#2}%
}
\makeatother
\newcommand{\urandomstring}[1]{%
\bashline{cat /dev/urandom | tr -dc "A-Za-z0-9" | fold -c#1 | head -1}%
}
\newcommand{\bashdate}{%
\bashline{date --iso-8601}%
}
\newcommand{\bashdatetime}{%
\bashline{date --iso-8601=seconds}%
}
\newcommand{\commit}{%
\bashline{git describe --dirty }%
}
\newcommand{\commitlog}{%
\bashline{git log -1 --oneline}%
}
\newcommand{\branch}{%
\bashline{git describe --all}%
}
\endinput
它基于Antal 的回答 在“如何将 shell 输出保存到 LaTeX 中的变量?”中。例如,检查\urandomstring
。每次调用时都会生成新的随机字符串。另请参阅\bashlines
宏。它对我来说就像本机一样工作bash
。
下面是一个例子: https://www.sharelatex.com/project/580e8926fe7b0dfd2ef8ae52
正如您所看到的,\bashdatetime
每次给出不同的纳秒。
答案4
你可以使用我的包裹iexec
:
\documentclass{minimal}
\usepackage{iexec}
\begin{document}
File listing is:
\iexec{ls /usr}
\end{document}