R 脚本中的问题
我试图了解here()如何以可移植的方式工作。找到了:看看下面有什么工作最终答案 - TL;DR- 底线,here()
运行 a 并不是真的那么有用script.R
从命令行。
我在 JBGruber 的帮助下理解它的方式:here()
查找项目的根目录(例如,RStudio 项目、Git 项目或使用 .here 文件定义的其他项目)从当前工作目录开始并向上移动,直到找到任何项目。如果没有找到任何内容,它将回退到使用完整的工作目录。如果由 cron 运行脚本,则默认为我的主目录。当然,可以通过 cron 命令将目录作为参数传递,但这相当麻烦。下面的答案提供了很好的解释,我在“最终答案部分”总结了我发现最直接有用的内容。但别误会,尼古拉的回答非常好,也很有帮助。
最初的目标- 编写一组R脚本,包括R-markdown.Rmd
这样我就可以压缩目录,发送给其他人,然后它就可以在他们的计算机上运行。可能在非常低端的计算机上 - 例如 RaspberryPi 或运行 Linux 的旧硬件。
状况:
- 可以通过命令行运行
Rscript
- 如上所述,但通过安排
cron
- 设置工作目录的主要方法是
set_here()
- 从控制台执行一次,然后该文件夹是可移植的,因为.here
文件包含在压缩目录中。
- 不需要
Rstudio
- 因此不想做 R 项目
-
can也可以交互地运行
Rstudio
(发展)
- 可以从执行
shiny
(我认为只要满足上述条件就可以了)
我特别不想创建 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"))
数据获取器.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()
将确保绝对基本路径。
Errors
当然 - 上面的方法是行不通的。仅当我执行时才有效Rscript ./dataFetcherMailer.R
来自同一个myGoodScripts/
目录。
My aim是了解如何执行脚本,以便相对于脚本位置解析相对路径,并且可以独立于当前工作目录从命令行运行脚本。现在只有完成后我才能从 bash 运行它cd
到包含脚本的目录。如果我安排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.
从 bash 与Rscript
如果 cwd 未设置为脚本目录。
$ 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
。因此只有basepath
from script.R
位置可以通用,而不是文件名。换句话说,这个函数当放置在.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
file 来获取文件的包含目录就足够了normalizePath(".")
- 无论你调用哪个都有效.Rmd
来自脚本、命令行或 Rstudio 的文件。