将 R 诊断消息发送到 stdout 而不是 stderr

问题描述 投票:0回答:5

寻找一个选项,让我可以将 R 诊断消息(由

message()
生成)重定向到
stdout
,而不是默认情况下的
stderr

message
手动状态:

默认处理程序将消息发送到 stderr() 连接。

所以问题是如何改变这种默认行为?仍然保留

warning()
stop()
的重定向完好无损。

已经尝试过接收器

type='message'
,但它会重定向所有内容(消息、警告、错误)。

如果有人愿意测试,这是示例脚本

exec_test.R

print("using print")
cat("using cat\n")
message("using message")
warning("using warning")
stop("using stop")
q("no")

然后将由以下人员执行:

Rscript exec_test.R 1>> exec_test.Rout 2>> exec_test_error.Rout

我不知道该使用什么

2>&1
,因为我的脚本会产生大量的
message
,而且很少出现真正的错误,所以我需要将这些日志存储在单独的文件中。

r stdout message stderr
5个回答
5
投票

使用

sink
。这是对您的代码的修改:

sink(stdout(), type = "message") # sink messages to stdout
print("using print")
cat("using cat\n")
message("using message")
warning("using warning")
sink(NULL, type="message") # close the sink
warning("after ending sink") # this will be the only thing in your err file
q("no")

4
投票

OP 显示了通过 Rscript 命令并使用一些 I/O 重定向进行的执行。如果您想使用重定向来记录所有内容并仅在出现错误时显示到控制台,我发现的最佳方法是使用

||
在打印到屏幕之前检查脚本是否具有非零退出状态:

Rscript myrscript.R > temp.log 2>&1 || cat temp.log

此方法严格依赖于打印的退出代码,它只能部分绕过

message()
进入 stderr,但我认为这个示例有助于提及,因为消息不一定会触发非零退出状态,您可以继续用这种方法安静地记录。

如果您想更进一步并继续附加到单个日志文件,那么这将起作用:

Rscript myrscript.R > temp.log 2>&1 || cat temp.log && cat temp.log >> persistent.log && rm temp.log

该命令的伪代码是:

  1. stderr
    stdout
    重定向到 temp.log
  2. 如果命令具有非零退出状态,则写入屏幕
  3. 然后将 temp.log 的内容重定向到你的 permanent.log 中
  4. 然后删除temp.log

2
投票

虽然这很可能不是最佳实践,但您可以使用默认写入

message
的版本覆盖
stdout()
,对吗?

message <- function (..., domain = NULL, appendLF = TRUE) 
{
    args <- list(...)
    cond <- if (length(args) == 1L && inherits(args[[1L]], "condition")) {
        if (nargs() > 1L) 
            warning("additional arguments ignored in message()")
        args[[1L]]
    }
    else {
        msg <- .makeMessage(..., domain = domain, appendLF = appendLF)
        call <- sys.call()
        simpleMessage(msg, call)
    }
    defaultHandler <- function(c) {
        cat(conditionMessage(c), file = stdout(), sep = "")
    }
    withRestarts({
        signalCondition(cond)
        defaultHandler(cond)
    }, muffleMessage = function() NULL)
    invisible()
}

0
投票

将@Thomas 和@branch14 的答案合二为一,这就是我的想法:

.stderr_message <- message
message <- function(...) {
    sink(stdout(), type = "message")
    .stderr_message(...)
    sink(NULL, type = "message")
}

我使用此代码在 shell 上验证了这一点

.stderr_message <- message
message <- function(...) {
    sink(stdout(), type = "message")
    .stderr_message(...)
    sink(NULL, type = "message")
}

print(1)
message(1)

没有我的

message()
:

> Rscript bug.R 2> >(sed 's/^/stderr: /') > >(sed 's/^/stdout: /')
stdout: [1] 1
stderr: 1
> Rscript bug.R 2> >(sed 's/^/stderr: /') > >(sed 's/^/stdout: /')
stdout: [1] 1
stdout: 1

0
投票

这可能是一种解决方法,而不是推荐的解决方案,但应该是满足问题确切需求的最佳匹配。另一种方法是使用 C++ 钩子,但这超出了我的能力范围。

使用 @bers 代码,我们可以在会话期间替换基本 R 命名空间函数,我们可以在全局范围内更改警告和消息的行为。

留言

我们可以创建函数,然后使用以下方法替换名称空间中的基本 R 函数:

.stderr_message <- message
message <- function(...) {
  sink(stdout(), type = "message")
  .stderr_message(...)
  sink(NULL, type = "message")
}

assignInNamespace("message", message, "base")

现在我们可以测试消息了:

# message go to stdout()
library("dplyr") ## this should redirect the mask messages to stdout

警告

我们可以对警告消息执行相同的操作,但对于警告,我们应该添加

imediate. =TRUE
,否则它将收集所有消息并使用另一个函数(我不知道),该函数将使用默认接收器到 stderr。

.stderr_warning <- warning
warning <- function(...) {
  sink(stdout(), type = "message")
  .stderr_warning(..., immediate. = TRUE)
  sink(NULL, type = "message")
}
assignInNamespace("warning", warning, "base")

现在警告:

# warning go to stdout()
optFunc <- function(x) {
  abs(x^4 + x^2)
}
root <- optim(-1, optFunc)

但这仅适用于使用 R 的

base::warning
实现的函数,而不是来自
.Call
rcpp
的 C 警告,因为它们从 C 接口而不是 R env 调用直接警告和错误消息。一个例子:

## warning go to stderr()
as.numeric(c("1", "NA")) 

## this error and warning to stderr()
readRDS("noFile")
© www.soinside.com 2019 - 2024. All rights reserved.