【发布时间】:2021-04-02 03:22:54
【问题描述】:
R 脚本中的此处问题
我试图了解 here() 如何以可移植的方式工作。找到它:在 Final answer - TL;DR 下查看稍后的工作原理 - 最重要的是,here() 从命令行运行 script.R 并没有那么有用。
我在 JBGruber 的帮助下理解它的方式:here() 查找项目的根目录(例如,RStudio 项目、Git 项目或使用 .here 文件定义的其他项目)从当前开始工作目录并向上移动,直到找到任何项目。如果它没有找到任何东西,它会回退到使用完整的工作目录。如果是由 cron 运行的脚本,它将默认为我的主目录。当然,也可以通过 cron 命令将目录作为参数传递,但比较麻烦。下面的答案提供了很好的解释,我在“最终答案部分”下总结了我发现最直接有用的内容。但请不要误会,Nicola 的回答非常好,也很有帮助。
最初的目标 - 编写一组 R 脚本,包括 R-markdown .Rmd,这样我就可以压缩目录,发送给其他人,它可以在他们的计算机上运行。可能在非常低端的计算机上 - 例如 RaspberryPi 或运行 linux 的旧硬件。
条件:
- 可以通过
Rscript从命令行运行 - 同上,但通过
cron安排 - 设置工作目录的主要方法是
set_here()- 从控制台执行一次,然后文件夹是可移植的,因为.here文件包含在压缩目录中。 - 不需要
Rstudio- 因此不想做 R 项目 -
可以也可以从
Rstudio(开发)交互式运行 - 可以从
shiny执行(我假设满足以上条件就OK了)
我特别不想创建 Rstudio 项目,因为在我看来它需要安装和使用 Rstudio,但我希望我的脚本尽可能可移植,并在低资源、无头平台上运行。
示例代码:
假设工作目录为myGoodScripts,如下所示:
/Users/john/src/myGoodScripts/
开始开发时,我会使用setwd() 进入上述目录并执行set_here() 以创建.here 文件。然后有2个脚本dataFetcherMailer.R、dataFetcher.Rmd和一个子目录bkp:
dataFetcherMailer.R
library(here)
library(knitr)
basedir <- here()
# this is where here should give path to .here file
rmarkdown::render(paste0(basedir,"/dataFetcher.Rmd"))
# email the created report
# email_routine_with_gmailr(paste0(basedir,"dataFetcher.pdf"))
# now substituted with verification that a pdf report was created
file.exists(paste0(basedir,"/dataFetcher.pdf"))
dataFetcher.Rmd
---
title: "Data collection control report"
author: "HAL"
date: "`r Sys.Date()`"
output: pdf_document
---
```{r setup, include=FALSE}
library(knitr)
library(here)
basedir <- here()
# in actual program this reads data from a changing online data source
df.main <- mtcars
# data backup
datestamp <- format(Sys.time(),format="%Y-%m-%d_%H-%M")
backupName <- paste0(basedir,"/bkp/dataBackup_",datestamp,"csv.gz")
write.csv(df.main, gzfile(backupName))
```
# This is data collection report
Yesterday's data total records: `r nrow(df.main)`.
The basedir was `r basedir`
The current directory is `r getwd()`
The here path is `r here()`
我猜报告中的最后 3 行是匹配的。即使getwd() 与其他两个不匹配,也没关系,因为here() 将确保绝对基路径。
错误
当然 - 以上不起作用。只有当我从同一个 myGoodScripts/ 目录执行 Rscript ./dataFetcherMailer.R 时它才有效。
我的目标是了解如何执行脚本,以便相对于脚本的位置解析相对路径,并且可以从命令行独立于当前工作目录运行脚本。现在,只有在将 cd 完成到包含脚本的目录后,我才能从 bash 运行它。如果我安排 cron 执行脚本,默认工作目录将是 /home/user 并且脚本失败。我的天真方法是,无论 shell 的当前工作目录 basedir <- here() 是什么,都应该给出一个文件系统点,从该点可以解析相对路径是行不通的。
来自 Rstudio,没有事先 setwd()
here() starts at /home/user
Error in abs_path(input) :
The file '/home/user/dataFetcher.Rmd' does not exist.
如果 cwd 未设置到脚本目录,则从带有 Rscript 的 bash。
$ cd /home/user/scrc
$ Rscript ./myGoodScripts/dataFetcherMailer.R
here() starts at /home/user/src
Error in abs_path(input) :
The file '/home/user/src/dataFetcher.Rmd' does not exist.
Calls: <Anonymous> -> setwd -> dirname -> abs_path
如果有人能帮助我理解并解决这个问题,那就太好了。如果存在另一种在没有here() 的情况下设置基本路径的可靠方法,我很想知道。最终从Rstudio 执行脚本比了解如何从commandline/cron 执行此类脚本重要得多。
自 JBGruber 回答以来的更新:
我稍微修改了函数,以便它可以返回文件的文件名或目录。我目前正在尝试对其进行修改,以便当.Rmd 文件从 Rstudio 编织并同样通过 R 文件运行时它可以工作。
here2 <- function(type = 'dir') {
args <- commandArgs(trailingOnly = FALSE)
if ("RStudio" %in% args) {
filepath <- rstudioapi::getActiveDocumentContext()$path
} else if ("interactive" %in% args) {
file_arg <- "--file="
filepath <- sub(file_arg, "", grep(file_arg, args, value = TRUE))
} else if ("--slave" %in% args) {
string <- args[6]
mBtwSquotes <- "(?<=')[^']*[^']*(?=')"
filepath <- regmatches(string,regexpr(mBtwSquotes,string,perl = T))
} else if (pmatch("--file=" ,args)) {
file_arg <- "--file="
filepath <- sub(file_arg, "", grep(file_arg, args, value = TRUE))
} else {
if (type == 'dir') {
filepath <- '.'
return(filepath)
} else {
filepath <- "error"
return(filepath)
}
}
if (type == 'dir') {
filepath <- dirname(filepath)
}
return(filepath)
}
然而,我发现commandArgs() 是从 R 脚本继承而来的,即当 .Rmd 文档从 script.R 编织时,它们保持不变。因此,只有来自script.R 位置的basepath 可以通用,而不是文件名。换句话说,这个函数放在.Rmd 文件中时将指向调用script.R 路径而不是.Rmd 文件路径。
最终答案 (TL;DR)
因此,此函数的较短版本将更有用:
here2 <- function() {
args <- commandArgs(trailingOnly = FALSE)
if ("RStudio" %in% args) {
# R script called from Rstudio with "source file button"
filepath <- rstudioapi::getActiveDocumentContext()$path
} else if ("--slave" %in% args) {
# Rmd file called from Rstudio with "knit button"
# (if we placed this function in a .Rmd file)
file_arg <- "rmarkdown::render"
string <- grep(file_arg, args, value = TRUE)
mBtwQuotes <- "(?<=')[^']*[^']*(?=')"
filepath <- regmatches(string,regexpr(mBtwQuotes,string,perl = T))
} else if ((sum(grepl("--file=" ,args))) >0) {
# called in some other way that passes --file= argument
# R script called via cron or commandline using Rscript
file_arg <- "--file="
filepath <- sub(file_arg, "", grep(file_arg, args, value = TRUE))
} else if (sum(grepl("rmarkdown::render" ,args)) >0 ) {
# Rmd file called to render from commandline with
# Rscript -e 'rmarkdown::render("RmdFileName")'
file_arg <- "rmarkdown::render"
string <- grep(file_arg, args, value = TRUE)
mBtwQuotes <- "(?<=\")[^\"]*[^\"]*(?=\")"
filepath <- regmatches(string,regexpr(mBtwQuotes,string,perl = T))
} else {
# we do not know what is happening; taking a chance; could have error later
filepath <- normalizePath(".")
return(filepath)
}
filepath <- dirname(filepath)
return(filepath)
}
NB: 从 .Rmd 文件中到达文件的包含目录就足以调用 normalizePath(".") - 无论您从脚本调用 .Rmd 文件,它都有效,命令行或来自 Rstudio。
【问题讨论】:
-
我不清楚是什么问题。您可以发送该文件夹,任何收到它的人都可以像在 PC 上运行它一样运行它。也许您想从其他地方启动它?请说明什么不起作用。
-
谢谢,我添加了解释和错误信息。
-
结果非常好!我没有检查您涵盖的所有不同案例,但我很高兴您完成了我开始的内容:) 我会将其复制到我的答案中,以表明这是最终答案。