Determinar la ruta del script de ejecución

255

Tengo un script llamado foo.Rque incluye otro script other.R, que está en el mismo directorio:

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

Pero quiero Rencontrar eso other.Rsin importar el directorio de trabajo actual.

En otras palabras, foo.Rnecesita conocer su propio camino. ¿Cómo puedo hacer eso?

Franco
fuente
2
No. :( No he visto ninguna solución que realmente funcione. Aparte de la solución para simplemente pasar el directorio o usar una variable de entorno.
Frank
3
¡Sería increíble hacer scripts totalmente portátiles y ejecutables incluso para R neofites!
Etienne Low-Décarie
44
¡Parece que todas las respuestas requieren que ingrese la ruta en algún momento (al menos para obtener el archivo)! Sería genial si pudiera enviar a alguien una carpeta comprimida y ejecutar cualquier archivo de secuencia de comandos R dentro de esa carpeta leería y guardaría en esa carpeta.
Etienne Low-Décarie
10
este único problema podría convertirse en la razón por la que podría mudarme completamente a Python
Giacomo
55
@giac_man, siento que R está lleno de cientos de pequeños problemas como este que se suman para dificultar mucho el trabajo.
Michael Barton

Respuestas:

102

Aquí hay una solución simple para el problema. Este comando:

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

devuelve la ruta del archivo de script actual. Funciona después de que se guardó el guión.

esto.no.no.nick
fuente
44
A mi no me funciona. Ejecuto R en Windows. ¿Alguna idea?
Ehsan88
44
Obtuve el mismo error, con un script guardado y recién instalado y ejecuté R 3.2.0 en Windows ...
RalfB
27
Este error ocurre cuando intenta ejecutar dirname(sys.frame(1)$ofile)directamente desde Rstudio. Funciona bien cuando el script se ejecuta usando source ("other.R"), y dirname(sys.frame(1)$ofile)está dentro "other.R".
Murta
44
Obtuve el error 'no hay tantos cuadros en la pila' cuando llamé como un script con rscript.exe, es decir, sin usar source (). así que tuve que usar la solución de Suppressingfire a continuación
Mark Adamson
3
Me NULLgelifico cuando esto se coloca en el servidor. R cuando uso brillo
Paul
75

Puede utilizar la commandArgsfunción para obtener todas las opciones que Rscript pasó al intérprete R real y buscarlas.--file= . Si su script se inició desde la ruta o si se inició con una ruta completa, lo script.namesiguiente comenzará con un '/'. De lo contrario, debe ser relativo al cwdy puede concatenar las dos rutas para obtener la ruta completa.

Editar: parece que solo necesitarías lo script.nameanterior y quitar el componente final de la ruta. Eliminé la cwd()muestra innecesaria, limpié el script principal y publiqué miother.R . Simplemente guarde este script y el other.Rscript en el mismo directorio chmod +x, y ejecute el script principal.

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)

otro.R :

print("hello")

salida :

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"

Esto es lo que creo que Dehmann está buscando.

Fuego supresor
fuente
¿Qué pasa con el downmod?
Suppressingfire
2
Modifiqué porque tu técnica no funciona sourcecomo pensé que quería el OP, pero tal vez leí mal su requisito. Pero no puedo deshacerme :( ¡Lo siento!
hadley
Pero en realidad, ¡funciona bien con la fuente! Solo fuente (other.name) y funciona correctamente.
Suppressingfire
3
Para la concatenación de rutas, mejor usarother.name <- file.path(script.basename, "other.R")
Jason
1
Cuando trato de ejecutar commandArgs(trailingOnly = FALSE)dentro de server.R en una aplicación brillante, me sale [1] "RStudio" "--interactive". No hay información sobre el directorio desde el que se llamó.
Paul
57

No pude lograr que la solución Suppressingfire funcionara cuando 'fuente' desde la consola R.
No pude conseguir que la solución de Hadley funcionara al usar Rscript.

¿Lo mejor de ambos mundos?

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))
        }
}
vapor25
fuente
66
Me gusta esto porque funciona con ambos Rscripty source()dentro de R. Sugeriría hacerlo normalizePath()en ambas versiones, de modo que proporcione la ruta completa en ambos casos.
wch
1
Esto es lo único que funcionó. Tenga en cuenta que para que esto funcione library(base)me llevó un tiempo entender eso jajaja
O.rka
2
Señor, obtenga mi voto, porque esta es la solución que funcionó para mí
Vince W.
1
Si esto ayuda a alguien, para la publicación original, eso significaría source(file.path(dirname(thisFile()), "other.R"))en foo.R. Esto funciona para mi.
Kim
Un problema Supongamos en RStudio que origen main.Rqué fuentes helper.Rqué llamadas thisFile(). Buscará el camino de en main.Rlugar de helper.R. ¿Algún consejo aquí?
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)]])

Sin embargo, no me preguntes cómo funciona, porque he olvidado: /

Hadley
fuente
2
¿En qué contexto funciona eso? print (sys.frames ()) aparece NULL cuando lo ejecuto.
Suppressingfire
1
@Suppressingfire: sys.framesdevuelve los entornos de la pila de llamadas, por lo que solo tiene sentido cuando se llama desde una función. Pruebe, por ejemplo, foo <- function() {bar <- function() print(sys.frames()); bar()}; foo(). Sin embargo, no puedo entender el código de @ hadley porque los entornos no tienen un ofilemiembro.
Richie Cotton
1
Tiene que obtener el archivo, es decir, si guardo ese código y luego lo ejecuto source("~/code/test.r"), PATHse establecerá en ~/desktop. Si solo lo evalúa en el nivel superior, devolverá NULL.
hadley el
44
Esto no responde mi pregunta. Necesito encontrar automáticamente el archivo "other.R". x$ofileestá indefinido, por lo que frame_filesestá vacío.
Frank
@hadley, código muy útil. Pude generalizar la función de utilidad "recargar script actual" que agrego a casi todos los scripts cuando están en desarrollo activo. Recargador de RScript
Sim
29

Esto funciona para mi

library(rstudioapi)    
rstudioapi::getActiveDocumentContext()$path
ColinTea
fuente
44
Esto solo funciona desde el interior de RStudio, supongo. Intentando desde la terminal que consigo Error: RStudio not running.
Ista
más específicamente funciona, si se ejecuta desde un script R en R studio. Incluso en la consola en RStudio no dará el resultado correcto ""en mi caso
Kay
Esto funciona mientras se ejecuta de forma interactiva en Rstudio siempre que no cambie el documento en foco . Si envía líneas para ejecutar y luego cambia a otro documento mientras se ejecutan, se devolverá la ruta al otro documento.
Patrick hace
26

La respuesta de rakensi de Getting path of a R script es la IMHO más correcta y realmente brillante. Sin embargo, sigue siendo un truco que incorpora una función ficticia. Lo estoy citando aquí, para que otros lo encuentren más fácilmente.

sourceDir <- getSrcDirectory (function (dummy) {dummy})

Esto proporciona el directorio del archivo donde se colocó la instrucción (donde se define la función ficticia). Luego se puede usar para establecer el directorio de trabajo y usar rutas relativas, por ejemplo

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

o para crear caminos absolutos

 source(paste(sourceDir, "/other.R", sep=""))
brazalete
fuente
1
Para mí, tu solución fue la mejor. Especialmente porque podría aplicarse a una aplicación Shiny y no a esa en el enlace.
jcarlos
1
Aquí getSrcDirectory es utils :: getSrcDirectory
RubenLaguna
55
Esto podría funcionar bien en Linux / Mac, pero no funcionó para mí en una sesión interactiva de RStudio en Windows. sourceDirEstaba en blanco.
Contango
1
@Contango en una terminal interactiva, no hay camino !!! Desea la ruta a un archivo.
pommedeterresautee
1
Me estoy poniendo character(0). Sugerencias?
abalter
16

Mi todo en uno! (--01 / 09/2019 actualizado para tratar con RStudio Console)

#' 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)
                }
            }
        }
    }
}
Jerry T
fuente
No funciona con sesión interactiva de R; Estoy obteniendo: `` `> source (" csf.R ")> csf () Error: RStudio no se
ejecuta`
Esto es genial. ¿Alguien puede hacer un paquete?
Joe Flack
Esto funciona mientras se ejecuta interactivamente en Rstudio siempre que no cambie el documento en foco. Si envía líneas para ejecutar y luego cambia a otro documento mientras se ejecutan, se devolverá la ruta al otro documento.
Patrick
13

Una variante reducida de la respuesta de Supressingfire:

source_local <- function(fname){
    argv <- commandArgs(trailingOnly = FALSE)
    base_dir <- dirname(substring(argv[grep("--file=", argv)], 8))
    source(paste(base_dir, fname, sep="/"))
}
momeara
fuente
Esto no funcionó recursivamente; el archivo que busco busca un archivo de datos (pero en el directorio incorrecto).
The Unfun Cat
11

Esto funciona para mi. Simplemente lo saca de los argumentos de la línea de comando, elimina el texto no deseado, hace un nombre de directorio y finalmente obtiene la ruta completa de eso:

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

He terminado y extendido las respuestas a esta pregunta en una nueva función thisfile()en rprojroot . También funciona para tejer con knitr.

krlmlr
fuente
6

Me gustó la solución de steamer25, ya que parece la más robusta para mis propósitos. Sin embargo, al depurar en RStudio (en Windows), la ruta no se configuraría correctamente. La razón es que si se establece un punto de interrupción en RStudio, el origen del archivo utiliza un comando alternativo de "fuente de depuración" que establece la ruta del script de forma un poco diferente. Aquí está la versión final que estoy usando actualmente que explica este comportamiento alternativo dentro de RStudio al depurar:

# @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))
        }
    }
}
aprstar
fuente
La fuente en Rstudio me ofreció, pero debugSource le dio a fileName para que su solución funcione bien, pero los comentarios del código no son correctos en mi caso
Mark Adamson
6

Intenté casi todo desde esta pregunta, Obtener la ruta de un script R , Obtener la ruta del script actual , Buscar la ubicación del archivo .R actual y el comando R para configurar el directorio de trabajo en la ubicación del archivo fuente en Rstudio , pero al final me encontré manualmente hojeando la tabla CRAN y encontrado

scriptName biblioteca

que proporciona la current_filename()función, que devuelve la ruta completa adecuada del script cuando se obtiene en RStudio y también cuando se invoca a través de R o RScript ejecutable.

Bojan P.
fuente
2
Package ‘scriptName’ was removed from the CRAN repository.- ¿ahora que? : o
Bojan P.
3

También tuve este problema, y ​​ninguna de las soluciones anteriores funcionó para mí. Quizás con elsource eso o cosas así, pero no estaba lo suficientemente claro.

Encontré esta solución, para mí elegante:

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

Lo importante es fileSnapshot()que le brinda mucha información sobre un archivo. Devuelve una lista de 8 elementos. Cuando elige pathcomo elemento de la lista, la ruta se devuelve con un \\separador, por lo que el resto del código es solo para cambiar eso.

Espero que esto ayude.

Antoine
fuente
1
Esto no funcionó para mí en una máquina Linux; en lugar de devolver la ruta del archivo, devolvió el directorio en el que me encontraba actualmente. Creé un script de prueba llamado TEST.R con una línea de código: print (fileSnapshot () $ path) Lo guardé en esta carpeta: / opt / home / boops / Desktop / Testfolder / TEST.RI luego navegó a mi escritorio e intentó ejecutar el archivo: boops @ linuxserver: ~ / Desktop $ Rscript /opt/home/boops/Desktop/Testfolder/TEST.R [1 ] "/ opt / home / boops / Desktop"
Boops Boops
Tampoco funcionó para mí. Devuelve lo mismo que 'here ()' cuando se usa la biblioteca 'here'. Devolvió la ruta a mi proyecto R abierto actualmente, pero no se está ejecutando el archivo.
Joe Flack
2

Puede ajustar el script r en un script bash y recuperar la ruta del script como una variable bash de la siguiente manera:

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

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

     EOF
ennuikiller
fuente
3
Esto requiere que tengas la ruta del script. No le permite crear un script R verdaderamente portátil que pueda ejecutarse desde cualquier lugar.
Etienne Low-Décarie
@ EtienneLow-Décarie No requiere la ruta del script, la obtiene de bash. El problema principal es que no es una forma confiable de obtener el camino. Se prefiere algo como esto, como en stackoverflow.com/questions/59895/… path_to_script = "$ (cd" $ (dirname "$ {BASH_SOURCE [0]}") "&& pwd)"
John Haberstroh
2

Me gusta este enfoque:

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

Acabo de resolver esto yo mismo. Para garantizar la portabilidad de su script, comience siempre con:

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

Funciona porque "." se traduce como el comando Unix $ PWD. Asignar esta cadena a un objeto de caracteres le permite insertar ese objeto de caracteres en setwd () y Presto su código siempre se ejecutará con su directorio actual como directorio de trabajo, sin importar en qué máquina se encuentre o en qué parte de la estructura de archivos se encuentre. situado. (Bonificación adicional: el objeto wd se puede usar con file.path () (es decir, file.path (wd, "output_directory") para permitir la creación de un directorio de salida estándar independientemente de la ruta del archivo que conduce a su directorio nombrado. Esto requiere que cree el nuevo directorio antes de hacer referencia a él de esta manera, pero eso también se puede ayudar con el objeto wd.

Alternativamente, el siguiente código realiza exactamente lo mismo:

wd <- getwd()
setwd(wd)

o, si no necesita la ruta del archivo en un objeto, simplemente puede:

setwd(".")
Andrew Moffat Jr.
fuente
11
No Eso encuentra el directorio del proceso, no el archivo en sí.
user1071847
Esto funcionó para mí en Windows con RStudio en modo interactivo.
Contango
2

Tenga en cuenta que el paquete getopt proporciona la get_Rscript_filenamefunción, que solo usa la misma solución presentada aquí, pero ya está escrita para usted en un módulo R estándar, por lo que no tiene que copiar y pegar la función "get script path" en cada script usted escribe.

Ryan C. Thompson
fuente
Siempre devuelve NA, incluso si creo un script que imprime su salida y luego llamo el script, por ejemplo, conR -e "library(getopt); testscript.R"
bokov
1
Como lo indica el nombre de la función, debe ejecutar su script usando Rscript.
Ryan C. Thompson
Ah, vaya. Gracias.
bokov
1

Consulte findSourceTraceback()el paquete R.utils , que

Encuentra todos los objetos 'srcfile' generados por source () en todos los marcos de llamadas. Esto hace posible averiguar qué archivos están actualmente programados por source ().

HenrikB
fuente
1

Tuve problemas con las implementaciones anteriores, ya que mi script se maneja desde un directorio con enlaces simbólicos, o al menos por eso creo que las soluciones anteriores no funcionaron para mí. En la línea de la respuesta de @ennuikiller, envolví mi Rscript en bash. Establezco la variable de ruta usando pwd -P, que resuelve las estructuras de directorios enlazados. Luego pase el camino hacia el 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)
Luke Singham
fuente
1

Usaría una variante del enfoque de @ steamer25. El punto es que prefiero obtener el último script de origen incluso cuando mi sesión se inició a través de Rscript. El siguiente fragmento, cuando se incluye en un archivo, proporcionará una variable que thisScriptcontiene la ruta normalizada del script. Confieso el uso (ab) de la fuente, por lo que a veces invoco Rscript y el script proporcionado en el --fileargumento genera otro script que genera otro ... Algún día invertiré para hacer que mi código desordenado se convierta en un paquete.

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))
  }
})()
Ailton Andrade de Oliveira
fuente
1

El 99% de los casos que podrías usar simplemente:

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

No funcionará para las llamadas de locos en el que el guión no es el primer argumento, es decir, source(some args, file="myscript"). Use @ hadley en estos casos elegantes.

antonio
fuente
Sin embargo, no desde dentro de RStudio, excepto cuando se
busca
1

El enfoque de Steamer25 funciona, pero solo si no hay espacios en blanco en el camino. En macOS, al menos, cmdArgs[match]devuelve algo así como /base/some~+~dir~+~with~+~whitespace/para /base/some\ dir\ with\ whitespace/.

Trabajé alrededor de esto reemplazando el "~ + ~" con un espacio en blanco simple antes de devolverlo.

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))
  }
}

Obviamente, aún puede extender el bloque else como lo hizo aprstar.

iball
fuente
1

Si en lugar de la secuencia de comandos, foo.Rconociendo su ubicación de ruta, si puede cambiar su código para que siempre haga referencia a todas sourcelas rutas 'd desde un comúnroot , estas pueden ser de gran ayuda:

Dado

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

Esto funcionará

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

Consulte https://rprojroot.r-lib.org/ para saber cómo definir las raíces del proyecto.

mmell
fuente
Para mí, el paquete aquí hace exactamente el trabajo y parece ser una solución fácil
Ron
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='/'))
Kinjelom
fuente
Todavía recibo el error "Error en sys.frame (1): no hay tantos cuadros en la pila"
Michael Barton
0

¡Es increíble que no haya una estructura tipo '$ 0' en R! Puede hacerlo con una llamada system () a un script bash escrito en R:

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

Luego solo divide el nombre scriptpath.sh para otro.

splitstr <- rev(strsplit(thisscript, "\\/")[[1]])
otherscript <- paste0(paste(rev(splitstr[2:length(splitstr)]),collapse="/"),"/other.R")
bruce.moran
fuente
Recibo un mensaje de errorreadLink: illegal option -- e usage: readLink [-FlLnqrsx] [-f format] [-t timefmt] [file ...]
altabq
0

Al observar la pila de llamadas, podemos obtener la ruta de archivo de cada script que se está ejecutando, los dos más útiles probablemente serán el script que se está ejecutando actualmente o el primer script que se obtendrá (entrada).

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) ))()
usuario425678
fuente