Determina il percorso dello script in esecuzione


255

Ho uno script chiamato foo.Rche include un altro script other.R, che si trova nella stessa directory:

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

Ma voglio Rscoprire che other.Rnon importa quale sia l'attuale directory di lavoro.

In altre parole, foo.Rdeve conoscere il proprio percorso. Come lo posso fare?


2
No. :( Non ho visto alcuna soluzione che funzioni davvero. A parte la soluzione alternativa, è sufficiente passare la directory o utilizzare una variabile di ambiente.
Frank

3
Sarebbe fantastico rendere gli script completamente portabili ed eseguibili anche da Neofiti R!
Etienne Low-Décarie,

4
Sembra che tutte le risposte richiedano di inserire il percorso ad un certo punto (almeno per trovare il file)! Sarebbe bello se potessi inviare a qualcuno una cartella compressa e eseguire qualsiasi file di script R all'interno di quella cartella leggerebbe e salvare in quella cartella.
Etienne Low-Décarie,

10
questo singolo numero potrebbe effettivamente diventare il motivo per cui potrei passare completamente a Python
Giacomo,

5
@giac_man, sento che R è pieno di centinaia di piccoli problemi come questo che si sommano per rendere molto difficile il lavoro.
Michael Barton

Risposte:


102

Qui c'è una soluzione semplice per il problema. Questo comando:

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

restituisce il percorso del file di script corrente. Funziona dopo che lo script è stato salvato.


4
Non funziona per me. Corro R in Windows. Qualche idea?
Ehsan88,

4
Ho avuto lo stesso errore, con uno scriptt salvato e appena installato ed eseguito R 3.2.0 su Windows ...
RalfB

27
Questo errore si verifica quando si tenta di eseguire dirname(sys.frame(1)$ofile)direttamente da Rstudio. Funziona bene quando lo script viene eseguito usando source ("other.R") ed dirname(sys.frame(1)$ofile)è all'interno "other.R".
Murta,

4
Ho avuto l'errore 'not that many frames on the stack' quando ho chiamato come script con rscript.exe cioè non usando source (). quindi ho dovuto invece utilizzare la soluzione di Suppressingfire di seguito
Mark Adamson,

3
Gel NULLquando questo viene inserito nel server.R quando si utilizza lucido
Paul

75

È possibile utilizzare la commandArgsfunzione per ottenere tutte le opzioni che sono state passate da Rscript all'interprete R attuale e cercarle --file=. Se lo script è stato avviato dal percorso o se è stato avviato con un percorso completo, il script.nameseguito inizierà con a '/'. Altrimenti, deve essere relativo alcwd e puoi concatenare i due percorsi per ottenere il percorso completo.

Modifica: sembra che avresti solo bisogno di quanto script.namesopra e di eliminare il componente finale del percorso. Ho rimosso il cwd()campione non necessario, ripulito lo script principale e pubblicato il mio other.R. Basta salvare questo script e lo other.Rscript nella stessa directory chmod +x, ed esegui lo script principale.

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)

altro.R :

print("hello")

uscita :

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"

Questo è ciò che credo dehmann stia cercando.


Cos'è il downmod?
Sopprimere il

2
Ho sminuito perché la tua tecnica non funziona sourcecome pensavo volesse l'OP - ma forse ho letto male le sue esigenze. Ma non riesco a smodellare :( Mi dispiace!
Hadley,

Ma in realtà, funziona bene con la fonte! Basta fonte (altro.nome) e funziona correttamente.
Sopprimere il

3
Per la concatenazione di percorsi, meglio usareother.name <- file.path(script.basename, "other.R")
Jason,

1
Quando provo a eseguire commandArgs(trailingOnly = FALSE)server.R in un'applicazione brillante, ottengo [1] "RStudio" "--interactive". Nessuna informazione sulla directory da cui è stata chiamata.
Paul,

57

Non riuscivo a far funzionare la soluzione di Suppressingfire durante il "source" dalla console R.
Non riuscivo a far funzionare la soluzione di Hadley quando usavo Rscript.

Il meglio di entrambi i mondi?

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
Mi piace perché funziona con entrambi Rscripte source()all'interno di R. Suggerirei di farlo normalizePath()su entrambe le versioni, in modo che fornisca il percorso completo in entrambi i casi.
dal

1
Questa è l'unica cosa che ha funzionato. Nota, perché questo funzioni library(base)mi ci è voluto un po 'per capire che lol
O.rka il

2
signore, ricevete il mio voto, perché questa è la soluzione che ha funzionato per me
Vince W.,

1
Se questo aiuta chiunque, per il post originale, che vorrebbe dire source(file.path(dirname(thisFile()), "other.R"))a foo.R. Questo funziona per me.
Kim,

Un problema. Supponiamo che in RStudio I fonte main.Rquali fonti helper.Rchiama thisFile(). Recupererà il percorso di main.Ranziché helper.R. Qualche consiglio qui?
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)]])

Non chiedermi come funziona, perché ho dimenticato: /


2
In quale contesto funziona? print (sys.frames ()) diventa NULL quando lo eseguo.
Soppressione del fuoco

1
@Suppressingfire: sys.framesrestituisce gli ambienti dello stack di chiamate, quindi ha davvero senso solo quando viene chiamato da una funzione. Prova, ad es foo <- function() {bar <- function() print(sys.frames()); bar()}; foo(). Non riesco a capire il codice di @ hadley perché gli ambienti non hanno un ofilemembro.
Richie Cotton,

1
Devi cercare il file in - cioè se salvo quel codice e poi eseguo source("~/code/test.r"), PATHsarà impostato su ~/desktop. Se lo si valuta al livello più alto, verrà restituito NULL.
Hadley,

4
Questo non risponde alla mia domanda. Devo trovare automaticamente il file "other.R". x$ofilenon è definito, quindi frame_filesè vuoto.
Frank,

@hadley, codice molto utile. Sono stato in grado di generalizzare la funzione di utilità "ricarica script corrente" che aggiungo a quasi tutti gli script quando sono in sviluppo attivo. RScript reloader
Sim,

29

Questo funziona per me

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

4
Funziona solo dall'interno di RStudio, suppongo. Provo dal terminal che ottengo Error: RStudio not running.
Ista,

più specificamente funziona, se eseguito da uno script R in R studio. Anche sulla console in RStudio non darà il risultato giusto ""nel mio caso
Kay

Funziona durante l'esecuzione interattiva in Rstudio purché non si modifichi il documento in primo piano . Se si inviano righe da eseguire e si passa a un altro documento mentre sono in esecuzione, verrà restituito il percorso dell'altro documento.
Patrick

26

La risposta di rakensi da Ottenere il percorso di uno script R è l'IMHO più corretto e davvero brillante. Tuttavia, è ancora un trucco che incorpora una funzione fittizia. Lo sto citando qui, per farlo trovare più facilmente da altri.

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

Ciò fornisce la directory del file in cui è stata posizionata l'istruzione (in cui è definita la funzione fittizia). Può quindi essere utilizzato per impostare la directory di lavoro e utilizzare percorsi relativi, ad es

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

o per creare percorsi assoluti

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

1
Per me, la tua soluzione è stata la migliore. Soprattutto perché potrebbe essere applicato a un'app Shiny e quella sul link no.
jcarlos,

1
Qui getSrcDirectory è utils :: getSrcDirectory
RubenLaguna,

5
Questo potrebbe funzionare bene su Linux / Mac, ma non ha funzionato per me in una sessione RStudio interattiva su Windows. sourceDirera vuoto.
Contango,

1
@Contango su un terminale interattivo, non c'è percorso !!! Vuoi il percorso di un file.
pommedeterresautee,

1
Sto ottenendo character(0). Suggerimenti?
abalter

16

Il mio tutto in uno! (--01 / 09/2019 aggiornato per gestire 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)
                }
            }
        }
    }
}

Non funziona con la sessione R interattiva; Sto ottenendo: `` `> source (" csf.R ")> csf () Errore: RStudio non in esecuzione` ``
ManicMailman

Questo è fantastico Qualcuno può fare un pacchetto?
Joe Flack,

Funziona durante l'esecuzione interattiva in Rstudio purché non si modifichi il documento in primo piano. Se si inviano righe da eseguire e si passa a un altro documento mentre sono in esecuzione, verrà restituito il percorso dell'altro documento.
Patrick

13

Una variante ridotta della risposta di Supressingfire:

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

Questo non ha funzionato in modo ricorsivo; il file I source cerca un file di dati (ma nella directory errata).
The Unfun Cat,

11

Questo funziona per me. Basta estrarlo dagli argomenti della riga di comando, rimuovere il testo indesiderato, fare un dirname e finalmente ottenere il percorso completo da quello:

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

8

Ho racchiuso ed esteso le risposte a questa domanda in una nuova funzione thisfile()in rprojroot . Funziona anche con la maglia knitr.


6

Mi è piaciuta la soluzione di steamer25 in quanto sembra la più robusta per i miei scopi. Tuttavia, durante il debug in RStudio (in Windows), il percorso non verrebbe impostato correttamente. Il motivo è che se un punto di interruzione è impostato in RStudio, l'approvvigionamento del file utilizza un comando "debug source" alternativo che imposta il percorso dello script in modo leggermente diverso. Ecco la versione finale che sto attualmente utilizzando, che tiene conto di questo comportamento alternativo all'interno di RStudio durante il debug:

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

la fonte in Rstudio mi ha dato ofile, ma debugSource ha dato fileName in modo che la tua soluzione funzioni bene ma i commenti sul codice non sono proprio nel mio caso
Mark Adamson

6

Ho provato quasi tutto da questa domanda, Ottenere il percorso di uno script R , Ottenere il percorso dello script corrente , Trovare la posizione del file .R corrente e il comando R per impostare la directory di lavoro sulla posizione del file di origine in Rstudio , ma alla fine mi sono trovato manualmente sfogliando la tabella CRAN e trovando

scriptName biblioteca

che fornisce la current_filename()funzione, che restituisce il percorso completo corretto dello script durante l'approvvigionamento in RStudio e anche quando si richiama tramite eseguibile R o RScript.


2
Package ‘scriptName’ was removed from the CRAN repository.- e adesso? : o
Bojan P.

3

Ho avuto anche questo problema, e nessuna delle soluzioni di cui sopra ha funzionato per me. Forse con il sourceo cose del genere, ma non era abbastanza chiaro.

Ho trovato questa soluzione per me elegante:

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

La cosa importante è fileSnapshot()che ti dà molte informazioni su un file. Restituisce un elenco di 8 elementi. Quando si seleziona pathcome elemento dell'elenco, il percorso viene restituito con \\come separatore, quindi il resto del codice serve solo a cambiarlo.

Spero che aiuti.


1
Questo non ha funzionato per me su una macchina Linux; invece di restituire il percorso del file, ha restituito la directory in cui mi trovavo attualmente. Ho creato uno script di test chiamato TEST.R con una riga di codice: print (fileSnapshot () $ path) L'ho salvato in questa cartella: / opt / home / boops / Desktop / Testfolder / TEST.RI quindi navigato sul mio desktop e ha cercato di eseguire il file: boops @ linuxserver: ~ / Desktop $ Rscript /opt/home/boops/Desktop/Testfolder/TEST.R [1 ] "/ opt / home / boops / Desktop"
Boops Boops

Neanche ha funzionato per me. Restituisce la stessa cosa di "qui ()" quando si utilizza la libreria "qui". Ha restituito il percorso al mio progetto R attualmente aperto, ma non è lo stesso file in esecuzione.
Joe Flack,

2

Puoi avvolgere lo script r in uno script bash e recuperare il percorso dello script come variabile bash in questo modo:

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

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

     EOF

3
Ciò richiede che tu abbia il percorso dello script. Non ti consente di creare uno script R veramente portatile che può essere eseguito da qualsiasi luogo.
Etienne Low-Décarie,

@ EtienneLow-Décarie Non richiede il percorso dello script, lo ottiene da bash. Il problema principale è che non è un modo affidabile per ottenere il percorso. Qualcosa di simile è preferito, come in stackoverflow.com/questions/59895/… path_to_script = "$ (cd" $ (dirname "$ {BASH_SOURCE [0]}") "&& pwd)"
John Haberstroh,

2

Mi piace questo approccio:

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

2

L'ho appena capito da solo. Per garantire la portabilità del tuo script, inizia sempre con:

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

Funziona perché "." si traduce come il comando Unix $ PWD. L'assegnazione di questa stringa a un oggetto carattere consente quindi di inserire l'oggetto carattere in setwd () e Presto il codice verrà sempre eseguito con la directory corrente come directory di lavoro, indipendentemente dal computer su cui si trova o dalla struttura del file trova. (Bonus extra: l'oggetto wd può essere utilizzato con file.path () (es. File.path (wd, "directory_output") per consentire la creazione di una directory di output standard indipendentemente dal percorso del file che porta alla directory denominata. Ciò richiede di creare la nuova directory prima di fare riferimento in questo modo, ma anche questo può essere aiutato con l'oggetto wd.

In alternativa, il codice seguente esegue esattamente la stessa cosa:

wd <- getwd()
setwd(wd)

oppure, se non hai bisogno del percorso del file in un oggetto, puoi semplicemente:

setwd(".")

11
No. Questo trova la directory del processo, non il file stesso.
user1071847

Questo ha funzionato per me in Windows con RStudio in modalità interattiva.
Contango

2

Si noti che il pacchetto getopt fornisce la get_Rscript_filenamefunzione, che utilizza solo la stessa soluzione presentata qui, ma è già scritta per te in un modulo R standard, quindi non è necessario copiare e incollare la funzione "get script path" in ogni script Scrivi.


Restituisce sempre NA, anche se creo uno script che stampa il suo output e quindi lo chiama ad esempio conR -e "library(getopt); testscript.R"
bokov

1
Come suggerisce il nome della funzione, è necessario eseguire lo script utilizzando Rscript.
Ryan C. Thompson,

Ah, oops. Grazie.
bokov,

1

Vedi findSourceTraceback()del pacchetto R.utils , che

Trova tutti gli oggetti 'srcfile' generati da source () in tutti i frame di chiamata. Ciò consente di scoprire quali file sono attualmente copiati da source ().


1

Ho avuto problemi con le implementazioni di cui sopra poiché il mio script è gestito da una directory con collegamenti simbolici, o almeno è per questo che penso che le soluzioni di cui sopra non abbiano funzionato per me. Sulla falsariga della risposta di @ennuikiller, ho racchiuso il mio Rscript in bash. Ho impostato la variabile path usando pwd -P, che risolve le strutture di directory con link simbolici. Quindi passa il percorso in 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

Vorrei usare una variante dell'approccio di @ steamer25. Il punto è che preferisco ottenere l'ultimo script di provenienza anche quando la mia sessione è stata avviata tramite Rscript. Il frammento seguente, quando incluso in un file, fornirà una variabile thisScriptcontenente il percorso normalizzato dello script. Confesso l'uso (ab) del source, quindi a volte invoco Rscript e lo script fornito --filenell'argomento genera un altro script che ne genera un altro ... Un giorno investirò per trasformare il mio codice disordinato in un pacchetto.

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% dei casi che potresti semplicemente usare:

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

Non funzionerà con chiamate folli in cui lo script non è il primo argomento, vale a dire source(some args, file="myscript"). Usa @ hadley in questi casi fantasiosi.


Non dall'interno di RStudio, però, tranne che per l'approvvigionamento
nJGL il

1

L'approccio di Steamer25 funziona, ma solo se non c'è spazio nel percorso. Su macOS almeno cmdArgs[match]ritorna qualcosa di simile /base/some~+~dir~+~with~+~whitespace/a/base/some\ dir\ with\ whitespace/ .

Ho risolto il problema sostituendo "~ + ~" con un semplice spazio bianco prima di restituirlo.

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

Ovviamente puoi comunque estendere il blocco else come ha fatto aprstar.


1

Se anziché lo script, foo.Rconoscendo la posizione del percorso, se è possibile modificare il codice per fare sempre riferimento a tutti sourcei percorsi da un comune, rootquesti potrebbero essere di grande aiuto:

Dato

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

Questo funzionerà

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

Vedi https://rprojroot.r-lib.org/ per come definire le radici del progetto.


Per me il pacchetto qui fa esattamente il lavoro e sembra essere una soluzione facile
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='/'))

Ottengo ancora l'errore "Errore in sys.frame (1): non molti frame nello stack"
Michael Barton,

0

Incredibile non esiste una struttura di tipo "$ 0" in R! Puoi farlo con una chiamata di sistema () a uno script bash scritto in R:

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

Quindi dividi il nome scriptpath.sh per other.R

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

Ricevo un messaggio di errorereadLink: illegal option -- e usage: readLink [-FlLnqrsx] [-f format] [-t timefmt] [file ...]
altabq

0

Osservando lo stack di chiamate, è possibile ottenere il percorso del file di ogni script in esecuzione, i due più utili saranno probabilmente lo script attualmente in esecuzione o il primo script da reperire (voce).

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) ))()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.