在Shiny中能否展示用system(wait=T)运行的外部程序的运行日志?
实现方案
完全可以实现,核心逻辑是放弃processx::run(..., wait = TRUE)的全阻塞调用,改用processx的底层Process类手动控制进程轮询,在轮询间隙给Shiny预留UI刷新的时间窗口,既可以保证外部程序运行期间Shiny不会响应其他操作,又能实时更新输出内容。
具体实现步骤
- 第一步:使用
processx::Process启动目标外部程序,配置stdout = "|", stderr = "|"直接捕获程序输出,也可以保留你原有的日志输出配置,后续直接读取日志增量即可。 - 第二步:编写轮询循环,只要外部进程还在运行就重复执行以下逻辑:
- 读取最新的stdout、stderr输出(或日志文件的新增内容)
- 更新存储输出内容的响应式变量
- 调用
session$flushReact()触发Shiny的UI刷新 - 执行短时间
Sys.sleep控制轮询频率,避免占用过高CPU
- 第三步:进程退出后读取剩余未处理的输出,完成收尾逻辑再恢复Shiny的正常交互。
可运行示例代码
library(shiny) library(processx) # 若需使用按钮禁用功能请加载shinyjs包 # library(shinyjs) ui <- fluidPage( # shinyjs::useShinyjs(), # 启用shinyjs actionButton("run", "启动外部程序"), verbatimTextOutput("log_output") ) server <- function(input, output, session) { # 存储日志内容的响应式变量 log_content <- reactiveVal("") observeEvent(input$run, { # 禁用按钮避免重复触发 # shinyjs::disable("run") log_content("") # 替换为你的外部程序路径和运行参数 proc <- Process$new( command = "your_program.exe", args = c("arg1", "arg2"), stdout = "|", stderr = "|", poll_connection = TRUE ) # 进程轮询循环 while(proc$is_alive()) { # 读取最新输出 new_stdout <- proc$read_output_lines() new_stderr <- proc$read_error_lines() # 更新日志内容 if (length(new_stdout) > 0) { log_content(paste0(log_content(), paste(new_stdout, collapse = "\n"), "\n")) } if (length(new_stderr) > 0) { log_content(paste0(log_content(), "*错误输出:* ", paste(new_stderr, collapse = "\n"), "\n")) } # 触发UI刷新 session$flushReact() # 控制轮询间隔,可根据输出频率调整 Sys.sleep(0.2) } # 读取进程结束前剩余的未读输出 final_stdout <- proc$read_all_output_lines() final_stderr <- proc$read_all_error_lines() if (length(final_stdout) > 0) { log_content(paste0(log_content(), paste(final_stdout, collapse = "\n"), "\n")) } if (length(final_stderr) > 0) { log_content(paste0(log_content(), "*错误输出:* ", paste(final_stderr, collapse = "\n"), "\n")) } # 恢复按钮可用状态 # shinyjs::enable("run") }) output$log_output <- renderPrint({ cat(log_content()) }) } shinyApp(ui, server)
如果你坚持要使用原有的日志文件方案,只需把上述代码中读取进程输出的逻辑替换为读取日志文件的增量内容即可,其余UI刷新逻辑完全通用。
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

