如何捕获system()的输出

23

这个问题是由Rmarkdown不输出系统命令结果到HTML文件所激发的。由于某种原因,R中的system()(或system2())函数的输出不能被sink()capture.output()捕获,因此目前没有办法让knitr记录输出内容。例如,在R控制台中:

> system('ls')
DESCRIPTION
NAMESPACE
R
README.md
inst
man

然而在一个knitr文档中,您将看不到输出,因为capture.output(system('ls'))character(0),即无法捕获输出。当然,我可以像我在那个问题的答案中提到的那样做cat(system('ls', intern = TRUE), sep = '\n'),但这有点尴尬。我想知道是否有一种方法可以捕获system()的输出,而不使用intern = TRUEcat()


更新:请参见https://github.com/yihui/knitr/issues/1203以获取我提供的解决该问题的hack。

2个回答

5

我认为你无法做到这一点(至少在*nix系统上;我没有Windows/Mac系统),因为system似乎会隐式地返回执行的命令的值,而R似乎无法将命令的输出重定向到R控制台。

这是因为你的终端的stdout和R控制台的stdout不同。你在R会话中看到的是终端stdout和R进程输出的混合物。capture.output正在寻找R进程的输出,而不是父进程所有输出到stdout的输出。

你可以启动一个打印到stdout的进程,将其放在后台,然后启动R...你将在你的"R输出"中看到该进程的输出,类似于如果你从R运行了system("ping -c5 8.8.8.8")

josh@computer: /home/josh
> ping -c5 8.8.8.8 & R
[1] 5808
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=46 time=39.9 ms

R version 3.2.4 Revised (2016-03-16 r70336) -- "Very Secure Dishes"
Copyright (C) 2016 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

  Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

> 64 bytes from 8.8.8.8: icmp_seq=2 ttl=46 time=38.1 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=46 time=38.3 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=46 time=38.4 ms
64 bytes from 8.8.8.8: icmp_seq=5 ttl=46 time=38.3 ms

--- 8.8.8.8 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4003ms
rtt min/avg/max/mdev = 38.176/38.656/39.986/0.703 ms

> q()
Save workspace image? [y/n/c]: n
[1]+  Done                    ping -c5 8.8.8.8

josh@computer: /home/josh
> 

2
我认为返回值不可见并不是问题所在。我期望capture.output()捕获stdout,尽管这不是文档中所记录的(文档使用了一个模糊的词“output”,我不知道它是否意味着stdout)。例如,您可以capture.output((function() {print("Hello world!"); invisible("hi")})()),它会从print()中捕获Hello world!。我的实际问题是为什么print()的输出可以被捕获,但system()的输出却不能。要么是system()不写入stdout,要么是capture.output()并没有真正捕获stdout。 - Yihui Xie
@Yihui 我并不是 R 内部专家,但是 这行代码 似乎 表明 system 函数正在使用 stdout(与我的预期相反)。 - MichaelChirico
@MichaelChirico 感谢您深入研究C代码。那么我接下来的问题是,capture.output() 究竟捕获了什么样的输出。 - Yihui Xie
@Yihui:非常抱歉,我回答时已经很晚了,显然没理解问题。看看我修改过后是否有帮助。 - Joshua Ulrich
1
非常有趣。那么问题就变成了,是否有编写更广泛版本的capture.output的空间,该版本将拦截终端的stdout以及R控制台的输出? - MichaelChirico
显示剩余2条评论

5

您可以添加一个函数 knitr::system,它会屏蔽 base::system。用户可以像使用 system::base 一样使用它,但输出可以被 capture.output 捕获:

system <- function(...) {
  stopifnot(!any(names(list(...)) %in% "intern"))
  result <- base::system(..., intern = TRUE)
  print(result)
}

我承认,这有点不正规,老实说,我也不确定可能会产生哪些副作用。但我认为值得一试。


谢谢!我知道这个,但如果我能找到一种捕获system()输出的方法,我就不会用它。请看我在Joshua Ulrich答案下面的评论。 - Yihui Xie
@Yihui 并不是“那么”糟糕 - 特别是知道在system中使用的ignore.stdoutignore.stderr选项只会将>/dev/null2>/dev/null重定向添加到command :) - daroczig
不错的方法,我的担心是该解决方案等待系统将所有内容写入结果对象,然后作为一个整体打印。如果你想监视长时间运行且持续写入到重定向的控制台中的系统调用,则它是不可行的。但我没有其他的解决方案。 - user3490026

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接