Визначте шлях виконання сценарію


255

У мене є сценарій під назвою foo.R який включає інший скрипт other.R, який знаходиться в тому самому каталозі:

#!/usr/bin/env Rscript
message("Hello")
source("other.R")

Але я хочу R це знайти, other.Rнезалежно від того, який поточний робочий каталог.

Іншими словами, foo.Rпотрібно знати свій власний шлях. Як я можу це зробити?


2
Ні :( Я не бачив жодного рішення, яке насправді працює. Окрім способу просто передати каталог у або використовувати змінну середовища.
Френк

3
Це було б дивовижно зробити сценарії повністю портативними та виконуваними навіть R-неофітами!
Етьєн Низький Декарі

4
Схоже, всі відповіді вимагають ввести шлях у якийсь момент (принаймні, до джерела файлу)! Було б чудово, якби ви могли надіслати кому-небудь стиснуту папку і запустивши будь-який файл R-скрипту в цій папці, він прочитав би і зберег у цю папку.
Етьєн Низький Декарі

10
цей єдиний випуск насправді міг стати причиною, чому я міг повністю перейти на Python
Giacomo

5
@giac_man, я відчуваю, що R насичений сотнями таких крихітних проблем, які всі додають до того, що дуже важко працювати.
Майкл Бартон

Відповіді:


102

Тут є просте рішення проблеми. Ця команда:

script.dir <- dirname(sys.frame(1)$ofile)

повертає шлях до поточного файлу сценарію. Він працює після збереження сценарію.


4
Це не працює для мене. Я запускаю R в Windows. Будь-яка ідея?
Ehsan88

4
Отримала таку ж помилку, із збереженим скриптом і щойно встановленим і запущеним R 3.2.0 на Windows ...
RalfB

27
Ця помилка трапляється при спробі виконання dirname(sys.frame(1)$ofile)безпосередньо з Rstudio. Він працює нормально, коли сценарій виконується за допомогою джерела ("other.R") і dirname(sys.frame(1)$ofile)знаходиться всередині "other.R".
Мурта

4
Я отримав помилку "не так багато кадрів у стеці", коли викликав сценарій з rscript.exe, тобто не використовував source (). тож мені довелося замість цього використати рішення із Suppressingfire нижче
Марк Адамсон,

3
Я гелю, NULLколи це розміщено на сервері.R при використанні блискучих
Пол

75

Ви можете скористатися commandArgsфункцією, щоб отримати всі параметри, які Rscript передав фактичному R-перекладачеві, і шукати їх --file=. Якщо ваш скрипт був запущений з контуру або якщо він був запущений повним шляхом, script.nameнижче розпочнеться з а '/'. В іншому випадку вона повинна бути відносно cwdі ви можете сформулювати два шляхи, щоб отримати повний шлях.

Редагувати: це здається, що вам знадобиться лише script.nameвищезазначене та зняти кінцевий компонент шляху. Я видалив непотрібний cwd()зразок і очистив основний сценарій і розмістив свій other.R. Просто збережіть цей скрипт і other.Rсценарій в одній і тій же директорії chmod +x, і запустіть основний.

main.R :

#!/usr/bin/env Rscript
initial.options <- commandArgs(trailingOnly = FALSE)
file.arg.name <- "--file="
script.name <- sub(file.arg.name, "", initial.options[grep(file.arg.name, initial.options)])
script.basename <- dirname(script.name)
other.name <- file.path(script.basename, "other.R")
print(paste("Sourcing",other.name,"from",script.name))
source(other.name)

інше.R :

print("hello")

вихід :

burner@firefighter:~$ main.R
[1] "Sourcing /home/burner/bin/other.R from /home/burner/bin/main.R"
[1] "hello"
burner@firefighter:~$ bin/main.R
[1] "Sourcing bin/other.R from bin/main.R"
[1] "hello"
burner@firefighter:~$ cd bin
burner@firefighter:~/bin$ main.R
[1] "Sourcing ./other.R from ./main.R"
[1] "hello"

Це я вважаю, що деман шукає.


Що з молодиком?
Придушення

2
Я змінився, тому що ваша техніка не працює, sourceяк я вважав, що ОП хоче, але, можливо, я неправильно прочитав його / її вимогу. Але я не можу відключити :( Вибачте!
hadley

Але насправді це добре працює з джерелом! Просто джерело (other.name), і воно працює належним чином.
Придушення

3
Для з’єднання шляхів краще використовуватиother.name <- file.path(script.basename, "other.R")
Джейсон

1
Коли я намагаюся запустити commandArgs(trailingOnly = FALSE)всередині server.R у блискучому додатку, я отримую [1] "RStudio" "--interactive". Немає інформації про каталог, з якого він викликався.
Пол

57

Я не міг змусити рішення Suppressingfire працювати, коли 'джерело' з консолі R.
Я не міг змусити рішення хедлі працювати, коли використовував Rscript.

Кращий з обох світів?

thisFile <- function() {
        cmdArgs <- commandArgs(trailingOnly = FALSE)
        needle <- "--file="
        match <- grep(needle, cmdArgs)
        if (length(match) > 0) {
                # Rscript
                return(normalizePath(sub(needle, "", cmdArgs[match])))
        } else {
                # 'source'd via R console
                return(normalizePath(sys.frames()[[1]]$ofile))
        }
}

6
Мені це подобається, тому що він працює як з обома, так Rscriptі source()всередині Р. Я б запропонував зробити normalizePath()обидві версії, щоб вони дали повний шлях в обох випадках.
wch

1
Це єдине, що спрацювало. Зауважте, для роботи library(base)мені знадобилося певний час, щоб зрозуміти, що це lol
O.rka

2
Ви, сер, отримаєте мій голос, тому що це рішення, яке працювало на мене
Вінс В.

1
Якщо це допоможе комусь, для оригінальної публікації, це буде означати source(file.path(dirname(thisFile()), "other.R"))в foo.R. Це працює для мене.
Кім

Одне питання. Припустимо, в RStudio I джерело, main.Rяке джерело, helper.Rяке викликає thisFile(). Він main.Rзамість нього здобуде шлях helper.R. Якісь поради тут?
Wassadamo

37
frame_files <- lapply(sys.frames(), function(x) x$ofile)
frame_files <- Filter(Negate(is.null), frame_files)
PATH <- dirname(frame_files[[length(frame_files)]])

Не запитуйте мене, як це працює, тому що я забув: /


2
У якому контексті це працює? print (sys.frames ()) виявляється НУЛІ, коли я запускаю його.
Придушення

1
@Suppressingfire: sys.framesповертає оточення стека викликів, тому це дійсно має сенс лише при виклику з функції. Спробуйте, наприклад, foo <- function() {bar <- function() print(sys.frames()); bar()}; foo(). Я не можу розібратися в коді @ hadley, оскільки в середовищі немає ofileучасника.
Річі Коттон

1
Ви повинні створити файл у файлі - тобто якщо я збережу цей код, то запустіть source("~/code/test.r"), PATHбуде встановлено значення ~/desktop. Якщо ви просто оціните його на найвищому рівні, він поверне NULL.
хадлі

4
Це не відповідає на моє запитання. Мені потрібно автоматично знайти файл "other.R". x$ofileне визначено, тому frame_filesпорожньо.
Френк

@hadley, дуже корисний код. Мені вдалося узагальнити функцію утиліти "перезавантажити поточний сценарій", яку я додаю майже до всіх сценаріїв, коли вони перебувають в активному розвитку. Перезавантажувач RScript
Сім,

29

Це працює для мене

library(rstudioapi)    
rstudioapi::getActiveDocumentContext()$path

4
Це працює тільки зсередини RStudio, я думаю. Спробую з терміналу я дістаюсь Error: RStudio not running.
Іста

точніше він працює, якщо запускати зі сценарію R в R-студії. Навіть на консолі в RStudio це не дасть правильного результату ""в моєму випадку
Кей

26

Відповідь о rakensi з Getting path of R script - найправильніша та справді геніальна IMHO. Тим не менш, це все ще хак, що включає в себе фіктивну функцію. Я цитую це тут, щоб легше було знайти інших.

sourceDir <- getSrcDirectory (функція (манекен) {манекен})

Це дає каталог файлу, де було розміщено оператор (де визначена фіктивна функція). Потім він може бути використаний для встановлення робочої каталоги та використання відносних шляхів, наприклад

setwd(sourceDir)
source("other.R")

або створити абсолютні шляхи

 source(paste(sourceDir, "/other.R", sep=""))

1
Для мене ваше рішення було найкращим. Спеціально тому, що його можна застосувати до програми Shiny, а цього - за посиланням.
jcarlos

1
Тут getSrcDirectory - це утиліти: getSrcDirectory
RubenLaguna

5
Це може добре працювати під Linux / Mac, але це не спрацювало для мене в інтерактивній сесії RStudio під Windows. sourceDirбув порожнім.
Контанго

1
@Contango на інтерактивному терміналі, шляху немає !!! Ви хочете шлях до файлу.
pommedeterresautee

1
Я отримую character(0). Пропозиції?
abalter

16

Моє все в одному! (--01 / 09/2019 оновлено для роботи з консолею RStudio)

#' current script file (in full path)
#' @description current script file (in full path)
#' @examples
#' works with Rscript, source() or in RStudio Run selection, RStudio Console
#' @export
ez.csf <- function() {
    # http://stackoverflow.com/a/32016824/2292993
    cmdArgs = commandArgs(trailingOnly = FALSE)
    needle = "--file="
    match = grep(needle, cmdArgs)
    if (length(match) > 0) {
        # Rscript via command line
        return(normalizePath(sub(needle, "", cmdArgs[match])))
    } else {
        ls_vars = ls(sys.frames()[[1]])
        if ("fileName" %in% ls_vars) {
            # Source'd via RStudio
            return(normalizePath(sys.frames()[[1]]$fileName))
        } else {
            if (!is.null(sys.frames()[[1]]$ofile)) {
            # Source'd via R console
            return(normalizePath(sys.frames()[[1]]$ofile))
            } else {
                # RStudio Run Selection
                # http://stackoverflow.com/a/35842176/2292993
                pth = rstudioapi::getActiveDocumentContext()$path
                if (pth!='') {
                    return(normalizePath(pth))
                } else {
                    # RStudio Console
                    tryCatch({
                            pth = rstudioapi::getSourceEditorContext()$path
                            pth = normalizePath(pth)
                        }, error = function(e) {
                            # normalizePath('') issues warning/error
                            pth = ''
                        }
                    )
                    return(pth)
                }
            }
        }
    }
}

Не працює з інтерактивним сеансом R; Я отримую: `` `> source (" csf.R ")> csf () Помилка: RStudio не
працює`

Це чудово. Чи може хтось зробити пакет?
Джо Флак

13

Зменшений варіант відповіді Supressingfire:

source_local <- function(fname){
    argv <- commandArgs(trailingOnly = FALSE)
    base_dir <- dirname(substring(argv[grep("--file=", argv)], 8))
    source(paste(base_dir, fname, sep="/"))
}

Це не працювало рекурсивно; файл, з якого я джерело, шукає файл даних (але в неправильній директорії).
The Unfun Cat

11

Це працює для мене. Просто виводить його з аргументів командного рядка, знімає непотрібний текст, робить dirname і, нарешті, отримує повний шлях до цього:

args <- commandArgs(trailingOnly = F)  
scriptPath <- normalizePath(dirname(sub("^--file=", "", args[grep("^--file=", args)])))

8

Я завершив і розширив відповіді на це питання на нову функцію thisfile()в rprojroot . Також працює для в'язання с knitr.


6

Мені сподобалося рішення steamer25, оскільки воно здається найбільш надійним для моїх цілей. Однак при налагодженні в RStudio (у Windows) шлях не буде встановлений належним чином. Причина полягає в тому, що якщо в RStudio встановлено точку зламу, джерело пошуку файлу використовує альтернативну команду "джерело налагодження", яка встановлює шлях сценарію дещо інакше. Ось остаточна версія, яку я зараз використовую, яка обліковує цю альтернативну поведінку в RStudio при налагодженні:

# @return full path to this script
get_script_path <- function() {
    cmdArgs = commandArgs(trailingOnly = FALSE)
    needle = "--file="
    match = grep(needle, cmdArgs)
    if (length(match) > 0) {
        # Rscript
        return(normalizePath(sub(needle, "", cmdArgs[match])))
    } else {
        ls_vars = ls(sys.frames()[[1]])
        if ("fileName" %in% ls_vars) {
            # Source'd via RStudio
            return(normalizePath(sys.frames()[[1]]$fileName)) 
        } else {
            # Source'd via R console
            return(normalizePath(sys.frames()[[1]]$ofile))
        }
    }
}

Джерело в Rstudio дало для мене ofile, але debugSource дав fileName, тому ваше рішення працює добре, але коментарі до коду в моєму випадку не зовсім правильні
Марк Адамсон

6

Я спробував майже все з цього питання, пробираючи шлях R-скрипту , Знайдіть шлях поточного сценарію , Знайдіть розташування поточного .R-файлу та R-команду для встановлення робочого каталогу на розташування вихідного файлу в Rstudio , але наприкінці я опинився вручну переглянувши таблицю CRAN та знайшовши

scriptName бібліотека

яка забезпечує current_filename()функцію, яка повертає належний повний шлях сценарію при пошуку в RStudio, а також при виклику через R або RScript виконуваний файл.


1
Package ‘scriptName’ was removed from the CRAN repository.- що тепер? : o
Боян П.

3

У мене також була ця проблема, і жодне з перерахованих вище рішень не працювало на мене. Можливо, з sourceподібними або подібними речами, але це було недостатньо зрозуміло.

Я знайшов це для мене елегантне рішення:

paste0(gsub("\\", "/", fileSnapshot()$path, fixed=TRUE),"/")

Важливим у цьому є те, fileSnapshot()що дає вам багато інформації про файл. Він повертає список з 8 елементів. Коли ви вибираєте pathв якості елемента списку, шлях повертається \\як роздільник, тому решта коду - просто змінити це.

Я сподіваюся, що це допомагає.


1
Це не працювало для мене на машині Linux; замість того, щоб повернути шлях до файлу, він повернув каталог, в якому я зараз знаходився. Я створив тестовий скрипт під назвою TEST.R з одним рядком коду: print (fileSnapshot () $ path) Я зберег його в цій папці: / opt / home / boops / Desktop / Testfolder / TEST.RI потім перейшов на робочий стіл і спробував запустити файл: boops @ linuxserver: ~ / Desktop $ Rscript /opt/home/boops/Desktop/Testfolder/TEST.R [1 ] "/ opt / home / boops / Desktop"
Boops Boops

Не працював і для мене. Повертає те саме, що і "тут ()" при використанні бібліотеки "тут". Він повернув шлях до мого відкритого на даний момент проекту R, але він не сам файл виконується.
Джо Флак

2

Ви можете обернути r-скрипт у bash-скрипті та отримати шлях сценарію у вигляді змінної bash, як-от так:

#!/bin/bash
     # [environment variables can be set here]
     path_to_script=$(dirname $0)

     R --slave<<EOF
        source("$path_to_script/other.R")

     EOF

3
Для цього потрібно мати шлях сценарію. Це не дозволяє зробити справді портативний R-скрипт, який може працювати з будь-якого місця.
Етьєн Низький Декарі

@ EtienneLow-Décarie Не потрібен шлях сценарію, він отримує його з bash. Головне питання полягає в тому, що це не надійний спосіб пройти шлях. Щось подібне є кращим, як у stackoverflow.com/questions/59895/… path_to_script = "$ (cd" $ (dirname "$ {BASH_SOURCE [0]}") "&& pwd)"
Джон Хаберстрох

2

Мені подобається такий підхід:

this.file <- sys.frame(tail(grep('source',sys.calls()),n=1))$ofile
this.dir <- dirname(this.file)

2

Я просто сам це розробив. Щоб забезпечити портативність вашого сценарію, завжди починайте його:

wd <- setwd(".")
setwd(wd)

Це працює тому, що "." перекладається як команда Unix $ PWD. Призначення цього рядка символьному об'єкту дозволяє вам потім вставити цей символьний об'єкт у setwd () та Presto ваш код завжди працюватиме з його поточним каталогом як робочий каталог, незалежно від того, на якій машині він знаходиться чи де в структурі файлів він знаходиться розташований. (Додатковий бонус: об'єкт wd можна використовувати з file.path () (тобто. File.path (wd, "output_directory"), щоб дозволити створити стандартний каталог виводу незалежно від шляху файлу, який веде до названого каталогу. Це вимагає, щоб ви створили новий каталог, перш ніж посилатися на нього таким чином, але це також може допомогти об'єкту wd.

Крім того, наступний код виконує абсолютно те саме:

wd <- getwd()
setwd(wd)

або, якщо вам не потрібен шлях до файлу в об’єкті, ви можете просто:

setwd(".")

11
Ні. Це знаходить каталог процесу, а не сам файл.
користувач1071847

Це працювало для мене в Windows з RStudio в інтерактивному режимі.
Контанго

2

Зауважте, що пакет getopt забезпечує get_Rscript_filenameфункцію, яка просто використовує те саме рішення, представлене тут, але вже написане для вас у стандартному модулі R, тому вам не доведеться копіювати та вставляти функцію "отримати шлях до сценарію" у кожен сценарій Ви пишете.


Він завжди повертає NA, навіть якщо я створю сценарій, який друкує його результат, а потім викликаю сценарій, наприклад, зR -e "library(getopt); testscript.R"
bokov

1
Як випливає з назви функції, вам потрібно запустити сценарій за допомогою Rscript.
Райан К. Томпсон,

Ах, ой. Дякую.
bokov

1

Дивіться findSourceTraceback()про пакет R.utils , який

Знаходить усі об’єкти 'srcfile', згенеровані джерелом (), у всіх кадрах викликів. Це дає змогу з’ясувати, які файли в даний час сценарії джерелом ().


1

У мене були проблеми з реалізаціями, наведеними вище, оскільки мій скрипт управляється з каталогів зв'язаним посиланням, або, принаймні, тому я вважаю, що вищевказані рішення не працювали для мене. По лінії відповіді @ ennuikiller я загорнув свій Rscript в баш. Я встановлюю змінну шляху за допомогою pwd -P, яка розв'язує структуру каталогів зв'язаних посилань. Потім пройдіть шлях у Rscript.

Bash.sh

#!/bin/bash

# set path variable
path=`pwd -P`

#Run Rscript with path argument
Rscript foo.R $path

foo.R

args <- commandArgs(trailingOnly=TRUE)
setwd(args[1])
source(other.R)

1

Я б застосував варіант підходу @ steamer25. Справа в тому, що я вважаю за краще отримати останній скрипт, навіть коли мій сеанс був запущений через Rscript. Наступний фрагмент, включений у файл, надасть змінну, thisScriptщо містить нормалізований шлях сценарію. Я визнаю (ab) використання source'ing, тому іноді я посилаюсь на Rscript і сценарій, наданий в --fileаргументі, джерело іншого сценарію, який джерело іншого ... Колись я вкладу гроші в те, щоб мій брудний код перетворився на пакет.

thisScript <- (function() {
  lastScriptSourced <- tail(unlist(lapply(sys.frames(), function(env) env$ofile)), 1)

  if (is.null(lastScriptSourced)) {
    # No script sourced, checking invocation through Rscript
    cmdArgs <- commandArgs(trailingOnly = FALSE)
    needle <- "--file="
    match <- grep(needle, cmdArgs)
    if (length(match) > 0) {
      return(normalizePath(sub(needle, "", cmdArgs[match]), winslash=.Platform$file.sep, mustWork=TRUE))
    }
  } else {
    # 'source'd via R console
    return(normalizePath(lastScriptSourced, winslash=.Platform$file.sep, mustWork=TRUE))
  }
})()

1

99% випадків, які ви можете просто використовувати:

sys.calls()[[1]] [[2]]

Він не працює для божевільних дзвінків, де сценарій не є першим аргументом, тобто source(some args, file="myscript"). Використовуйте @ hadley's у цих вигадливих випадках.


Не зсередини RStudio, окрім випадків, коли під час пошуку
nJGL

1

Підхід Steamer25 працює, але тільки якщо на шляху немає пробілу. У macOS принаймні cmdArgs[match]повертає щось подібне /base/some~+~dir~+~with~+~whitespace/до /base/some\ dir\ with\ whitespace/.

Я вирішив це, замінивши "~ + ~" простим пробілом перед поверненням.

thisFile <- function() {
  cmdArgs <- commandArgs(trailingOnly = FALSE)
  needle <- "--file="
  match <- grep(needle, cmdArgs)
  if (length(match) > 0) {
    # Rscript
    path <- cmdArgs[match]
    path <- gsub("\\~\\+\\~", " ", path)
    return(normalizePath(sub(needle, "", path)))
  } else {
    # 'source'd via R console
    return(normalizePath(sys.frames()[[1]]$ofile))
  }
}

Очевидно, ви можете продовжити ще один блок, як це зробив aprstar.


1

Якщо замість сценарію, foo.Rзнаючи його місце розташування, якщо ви можете змінити свій код, щоб завжди посилатися на всі source'd шляхи із загального, rootто це може бути чудовою допомогою:

Дано

  • /app/deeply/nested/foo.R
  • /app/other.R

Це спрацює

#!/usr/bin/env Rscript
library(here)
source(here("other.R"))

Див. Https://rprojroot.r-lib.org/ про те, як визначити корені проекту.


Для мене пакунок тут виконує саме таку роботу і, здається, це просте рішення
Рон

0
#!/usr/bin/env Rscript
print("Hello")

# sad workaround but works :(
programDir <- dirname(sys.frame(1)$ofile)
source(paste(programDir,"other.R",sep='/'))
source(paste(programDir,"other-than-other.R",sep='/'))

Я все ще отримую помилку "Помилка в sys.frame (1): не так багато кадрів на стеці"
Майкл Бартон

0

Дивовижно, що в R немає структури типу "$ 0"! Ви можете зробити це за допомогою системного () виклику до bash-сценарію, написаного на R:

write.table(c("readlink -e $0"), file="scriptpath.sh",col=F, row=F, quote=F)
thisscript <- system("sh scriptpath.sh", intern = TRUE)

Тоді просто розділіть ім'я scriptpath.sh для other.R

splitstr <- rev(strsplit(thisscript, "\\/")[[1]])
otherscript <- paste0(paste(rev(splitstr[2:length(splitstr)]),collapse="/"),"/other.R")

Я отримую повідомлення про помилкуreadLink: illegal option -- e usage: readLink [-FlLnqrsx] [-f format] [-t timefmt] [file ...]
altabq

0

Переглядаючи стек викликів, ми можемо отримати файловий шлях кожного сценарію, який виконується, два найбільш корисні, мабуть, або сценарій, що виконується в даний час, або перший скрипт, який буде отримано (запис).

script.dir.executing = (function() return( if(length(sys.parents())==1) getwd() else dirname( Filter(is.character,lapply(rev(sys.frames()),function(x) x$ofile))[[1]] ) ))()

script.dir.entry = (function() return( if(length(sys.parents())==1) getwd() else dirname(sys.frame(1)$ofile) ))()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.