Я пытаюсь понять, как здесь() будет работать переносимым способом. Найдено: посмотрите, что работает позже, в разделе «Окончательный ответ» — TL; DR — в нижней строке, here()
на самом деле не так уж полезно запускать script.R
из командной строки.
Как я это понимаю с помощью JBGruber: here()
ищет корневой каталог проекта (например, проект RStudio, проект Git или другой проект, определенный с помощью файла .here), начиная с текущего рабочего каталога и продвигаясь вверх, пока не найдет любой проект. Если он ничего не находит, он возвращается к использованию полного рабочего каталога. Что в случае запуска скрипта cron по умолчанию будет моим домашним каталогом. Можно, конечно, передать каталог в качестве параметра через команду cron, но это довольно громоздко. Ниже ответы дают хорошие объяснения, и я резюмировал то, что я нашел наиболее полезным в разделе «Окончательный ответ». Но не заблуждайтесь, ответ Николы тоже очень хорош и полезен.
Первоначальная цель — написать набор R-скриптов, включая R-markdown .Rmd
, чтобы я мог заархивировать каталог, отправить кому-то другому, и он запустился на их компьютере. Потенциально на очень слабом компьютере, таком как RaspberryPi или на старом оборудовании под управлением Linux.
Условия:
Rscript
cron
set_here()
- выполняется один раз из консоли, а затем папка становится переносимой, поскольку файл .here
включен в заархивированный каталог.Rstudio
- следовательно не хочет делать R-проектыRstudio
(разработка)shiny
(я предполагаю, что все будет в порядке, если будут выполнены вышеуказанные условия)Я специально не хочу создавать проекты Rstudio, потому что, на мой взгляд, это требует установки и использования Rstudio, но я хочу, чтобы мои сценарии были как можно более переносимыми и работали на платформах с низким уровнем ресурсов, без головы.
Предположим, что рабочий каталог будет myGoodScripts
следующим образом:
/Users/john/src/myGoodScripts/
при запуске разработки я бы перешел в указанный выше каталог с помощью setwd()
и выполнил set_here()
, чтобы создать .here
файл. Далее идут 2 скрипта dataFetcherMailer.R
, dataFetcher.Rmd
и поддиректория bkp
:
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"))
---
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()
обеспечит абсолютный базовый путь.
Конечно - вышеописанное не работает. Это работает, только если я запускаю Rscript ./dataFetcherMailer.R
из того же myGoodScripts/
каталога.
Моя цель - понять, как выполнять сценарии, чтобы относительные пути разрешались относительно местоположения сценария, и сценарий можно было запускать из командной строки независимо от текущего рабочего каталога. Теперь я могу запустить это из bash, только если я сделал cd
в каталоге, содержащем скрипт. Если я назначу cron
выполнение сценария, рабочим каталогом по умолчанию будет /home/user
, и сценарий завершится ошибкой. Мой наивный подход, который независимо от текущего рабочего каталога оболочки 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
.
Я немного изменил функцию, чтобы она могла возвращать либо имя файла, либо каталог для файла. В настоящее время я пытаюсь изменить его, чтобы он работал, когда файл .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
путь к файлу.
Поэтому более короткая версия этой функции будет более полезной:
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.
Спасибо, я добавил объяснение и сообщения об ошибках.
Это получилось весьма красиво! Я не проверял все различные случаи, которые вы освещаете, но я рад, что вы закончили то, что я начал :) Я скопирую это в свой ответ, чтобы показать, что это окончательный ответ.
Думаю, поведение here()
не совсем то, что вам нужно. Вместо этого вам нужно определить путь к исходному файлу, также известному как файл .R
. Я немного расширил команду here()
, чтобы она работала так, как вы ожидаете:
here2 <- function() {
args <- commandArgs(trailingOnly = FALSE)
if ("RStudio" %in% args) {
dirname(rstudioapi::getActiveDocumentContext()$path)
} else {
file_arg <- "--file = "
filepath <- sub(file_arg, "", grep(file_arg, args, value = TRUE))
dirname(filepath)
}
}
Идея для случая, когда скрипт не запускается в RStudio, исходит из этого ответа. Я попробовал это, вставив определение функции в начало вашего файла dataFetcherMailer.R
. Вы также можете подумать о том, чтобы поместить это в другой файл в вашем домашнем каталоге и вызвать его, например, source("here2.R")
вместо library(here)
, или вы можете написать небольшой пакет R для этой цели.
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)
}
Я нашел этот способ некоторое время назад, но затем фактически полностью изменил свой рабочий процесс, чтобы использовать только файлы R Markdown (и проекты RStudio). Одним из преимуществ этого является то, что рабочий каталог файлов Rmd всегда является местом расположения файла. Таким образом, вместо того, чтобы заморачиваться с настройкой рабочего каталога, вы можете просто написать все пути в своем скрипте относительно местоположения файла Rmd.
---
title: "Data collection control report"
author: "HAL"
date: "`r Sys.Date()`"
output: pdf_document
---
```{r setup, include=FALSE}
library(knitr)
# 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")
# create bkp folder if it doesn't exist
if (!dir.exists(paste0("./bkp/"))) dir.create("./bkp/")
backupName <- paste0("./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 current directory is `r getwd()`
Обратите внимание, что пути, начинающиеся с ./
, означают начало в папке файла Rmd. ../
означает, что вы поднимаетесь на один уровень вверх. ../../
вы поднимаетесь на два уровня вверх и так далее. Поэтому, если ваш файл Rmd находится в папке с именем «скрипты» в вашей корневой папке, и вы хотите сохранить свои данные в папке с именем «данные» в вашей корневой папке, вы пишете saveRDS(data, "../data/dat.RDS")
.
Вы можете запустить файл Rmd из командной строки/cron с помощью Rscript -e 'rmarkdown::render("/home/johannes/Desktop/myGoodScripts/dataFetcher.Rmd")'
.
Это выглядит великолепно и может просто решить эту проблему. Я проверю это и вернусь через час (когда доберусь до своего ноутбука)
Я проверил, и это работает очень хорошо. Благодаря вашей функции и объяснению я даже понимаю, как это работает. Будучи непрограммистом, я только задаюсь вопросом, какая польза от аргумента/параметра variables
в объявлении function()
. Но я должен быть в состоянии воспроизвести это в своих более сложных сценариях.
Извините, variables
был просто остатком от шаблона функции, который я использовал. Я исправил это!
Я еще немного подумал об этом и добавил альтернативный ответ, который, вероятно, лучше соответствует вашим потребностям.
Спасибо, Йоханнес, это очень полезно. Я знал об относительных путях при вязании .Rmd
, но элегантность вашей функции в том, что ее можно просто добавить в файл и она работает. Мне нужно будет передать эти сценарии коллегам, которые часто не знают, что такое путь. В данный момент пытаюсь модифицировать вашу функцию, чтобы она работала и при вязании Rmd из Rstudio. Я вставил измененную функцию в свой исходный вопрос, основываясь на том факте, что второй аргумент равен --slave
при вязании из Rstudio.
Ради интереса, @JBGruber, как бы вы кратко определили поведение here()
?
Я могу ошибаться, но: here()
ищет корневой каталог проекта (например, проект RStudio, проект Git или другой проект, определенный с помощью .here
файла), начиная с текущего рабочего каталога и продвигаясь вверх, пока не найдет какой-либо проект. Если он ничего не находит, он возвращается к использованию полного рабочего каталога. Меня постоянно смущало такое поведение, так как я ожидал, что поиск начнется с расположения файла, а не с рабочего каталога. Я рад, что у файлов .Rmd
нет этой проблемы, что делает их более полезными для меня.
Спасибо, именно это я и подозревал, но как-то не смог легко трут из пользовательской документации. Очень приятно знать вещи простым способом. Другая причина путаницы заключается в том, что часто это непоследовательно. Например. загрузите here
, введите setwd()
в консоли, подтвердите это с помощью getwd()
issue set_here()
для этого конкретного каталога, однако here()
и dr_here()
по-прежнему указывают на исходный рабочий каталог (по умолчанию домашний каталог). Мне нужно перезапустить сеанс R, чтобы here()
заметил. Я подозреваю, что это связано с предыдущим setwd()
предыдущим перезапуском сеанса R. Сбивает с толку.
На самом деле это сказано здесь: github.com/jennybc/here_here#the-fine-print, но я прочитал это первое предложение около 20 раз, прежде чем понял, что там говорится о рабочем каталоге.
Да, так оно и есть, но мне это тоже было не очевидно, когда я лихорадочно искал надежное решение. Введение в gitHub звучит примерно так, как here() — это решение всех задач, связанных с путями, но, как мне кажется, оно действительно больше относится к проектам, запускаемым из Rstudio или другого интерфейса, а не к простой командной строке.
Хотя ваш вопрос требует использования пакета here
, я предлагаю решение без необходимости. Я думаю, что это намного чище и одинаково портативно.
Если я правильно понимаю, вы хотите, чтобы ваш сценарий знал об их местонахождении. Это хорошо, но в большинстве случаев не нужно, потому что вызывающая сторона вашего скрипта должна знать, где находится скрипт, чтобы вызвать его, и вы можете использовать это знание. Так:
here
;Далее пару вариантов.
Первый, минимальный, — просто не регистрироваться на cron
голом Rstudio /path/to/yourfolder/yourscript.R
, а создать bash-скрипт следующим образом (назовем его script.sh
):
#!/bin/sh
cd /path/to/yourfolder
Rscript yourscript.R
и зарегистрируйте этот скрипт на crontab
. Вы можете добавить в свою папку файл README
, когда вы дадите указание сделать это (что-то вроде: «извлеките папку куда хотите, запишите путь, создайте файл script.sh
и создайте его в crotab»). Конечно, с помощью Rstudio вы можете открыть и запустить файл обычным способом (setwd
, а затем запустить его; вы документируете его в README
).
Второй — написать «установщик» (вы можете выбрать makefile
, простой R-скрипт, bash-файл или что-то еще), который сделает все вышеперечисленное автоматически. Он просто выполняет эти шаги.
.robertsProject
(обратите внимание, что точка более вероятна, что каталог не существует)..sh
точно так же, как и выше (обратите внимание, что вы знаете, куда вы перемещаете файлы и их местоположение, поэтому вы можете указать правильный путь в сценарии)..sh
в crontab.Сделанный! Тот, кто получит файл, должен будет просто запустить этот установщик один раз (вы задокументируете, как это сделать в README), и они смогут использовать ваш инструмент.
Спасибо, это очень полезно. Возможно, я реализую это в будущем. Однако на данный момент мне нужно что-то достаточно простое, чтобы другие пользователи могли открыть его в Rstudio и запустить, а также работать без изменений при вызове через cron
. С установщиком я столкнулся бы с препятствием, что большинство коллег будут использовать Windows 10, не будут знать, какой путь и не будут иметь прав администратора на своих компьютерах. Таким образом, основная цель состоит в том, чтобы конечные пользователи не беспокоились, если они запустят его на своих машинах; если дело доходит до размещения скрипта на безголовом сервере Linux, мне придется это сделать.
Я не вижу, как вышеприведенное решение является более сложным. Если вам нужно настроить задание cron, установщик намного проще для вас и для всех, кто хочет его использовать. Если пользователь хочет запустить сценарий только с помощью Rstudio, единственное, что нужно сделать перед «запуском сценария», — это «установить рабочий каталог» из меню, что вряд ли является сложной задачей. Дело в том, что пользователь как для запуска, так и для установки рабочего каталога должен знать, где находятся ваши файлы.
Спасибо, Никола, я думаю, что вы совершенно правы в целом, и я очень ценю ваш совет. Я обязательно попробую реализовать этот процесс позже. Однако причина моего вопроса заключалась в том, что мне нужно было понять, в чем разница между «местоположением» файла .R/.Rmd
при выполнении из командной строки или Rstudio. Что я теперь понимаю, пройдя оба ответа и обновив свой вопрос. Проблема с установщиком заключается в том, что другие люди, которым может понадобиться использовать скрипт, будут использовать Windows, поэтому cron
и bash
запутают их и кажутся почти невозможными.
Хорошо, но я исходил из того, что пользователю Windows не должно быть интересно cron
, и поэтому он может спокойно пропустить часть «установщика». Им просто нужно открыть Rstudio и установить рабочий каталог. Ваша разница с вашим запросом заключается в том, что они должны перейти непосредственно к «запуску сценария», но я не вижу дополнительных осложнений. Учтите, что наличие «домашнего» каталога — это то, что есть практически у каждого приложения и устанавливается во время установки. Существует не так много примеров программ, которые полагаются на путь, по которому они расположены во время выполнения.
Мне непонятно, в чем проблема. Вы можете отправить папку, и тот, кто ее получит, сможет запустить ее так же, как вы запускаете ее на своем ПК. Может быть, вы хотите запустить его откуда-то еще? Пожалуйста, покажите, что не работает.