Déterminer le chemin du script d'exécution


255

J'ai un script appelé foo.Rqui inclut un autre script other.R, qui se trouve dans le même répertoire:

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

Mais je veux Rtrouver que other.Rquel que soit le répertoire de travail actuel.

En d'autres termes, foo.Rdoit connaître son propre chemin. Comment puis je faire ça?


2
No. :( Je n'ai vu aucune solution qui fonctionne réellement. Mis à part la solution de contournement pour simplement passer le répertoire ou utiliser une variable d'environnement.
Frank

3
Ce serait incroyable de rendre les scripts entièrement portables et exécutables par même R neofites!
Etienne Low-Décarie

4
Il semble que toutes les réponses nécessitent que vous saisissiez le chemin à un moment donné (au moins pour obtenir le fichier)! Ce serait formidable si vous pouviez envoyer à quelqu'un un dossier compressé et que tout fichier de script R dans ce dossier serait lu et enregistré dans ce dossier.
Etienne Low-Décarie

10
ce seul problème pourrait en fait devenir la raison pour laquelle je pourrais complètement passer à Python
Giacomo

5
@giac_man, je pense que R est plein de centaines de petits problèmes comme celui-ci qui rendent le travail très difficile.
Michael Barton

Réponses:


102

Ici, il existe une solution simple au problème. Cette commande:

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

renvoie le chemin du fichier de script actuel. Cela fonctionne après que le script a été enregistré.


4
Ça ne marche pas pour moi. Je lance R sous Windows. Une idée?
Ehsan88

4
Vous avez la même erreur, avec un script enregistré et fraîchement installé et exécutez R 3.2.0 sur Windows ...
RalfB

27
Cette erreur se produit lorsque vous essayez d'exécuter dirname(sys.frame(1)$ofile)directement à partir de Rstudio. Cela fonctionne bien lorsque le script est exécuté à l'aide de source ("other.R"), et dirname(sys.frame(1)$ofile)est à l'intérieur "other.R".
Murta

4
J'ai eu l'erreur «pas beaucoup de cadres sur la pile» lors de l'appel en tant que script avec rscript.exe, c'est-à-dire en n'utilisant pas source (). j'ai donc dû utiliser à la place la solution de Suppressingfire ci
Mark Adamson

3
Je NULLgélifie lorsque cela est placé dans le serveur.R lors de l'utilisation de brillant
Paul

75

Vous pouvez utiliser la commandArgsfonction pour obtenir toutes les options passées par Rscript à l'interpréteur R réel et les rechercher --file=. Si votre script a été lancé à partir du chemin d'accès ou s'il a été lancé avec un chemin d'accès complet, ce qui script.namesuit commencera par un '/'. Sinon, il doit être relatif à la cwdet vous pouvez concaténer les deux chemins pour obtenir le chemin complet.

Modifier: il semble que vous n'ayez besoin que de ce qui script.nameprécède et que vous supprimiez le dernier composant du chemin. J'ai supprimé l' cwd()échantillon inutile , nettoyé le script principal et publié mon other.R. Enregistrez simplement ce script et le other.Rscript dans le même répertoire, chmod +xeux, et exécutez le 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)

autre.R :

print("hello")

sortie :

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"

C'est ce que je crois que dehmann recherche.


Quel est le downmod?
Suppressingfire

2
J'ai rétrogradé parce que votre technique ne fonctionne pas sourcecar je pensais que l'OP le voulait - mais peut-être que j'ai mal lu son exigence. Mais je ne peux pas dé-downmod :( Désolé!
hadley

Mais en fait, cela fonctionne bien avec la source! Juste source (autre.nom) et cela fonctionne correctement.
Suppressingfire

3
Pour la concaténation de chemin, mieux utiliserother.name <- file.path(script.basename, "other.R")
Jason

1
Quand j'essaye de courir à l' commandArgs(trailingOnly = FALSE)intérieur de server.R dans une application brillante, j'obtiens [1] "RStudio" "--interactive". Aucune information sur le répertoire d'où il a été appelé.
Paul

57

Je n'ai pas pu faire fonctionner la solution de Suppressingfire lors de la 'source' à partir de la console R.
Je n'ai pas pu faire fonctionner la solution de hadley lors de l'utilisation de Rscript.

Le meilleur des deux mondes?

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
J'aime cela car cela fonctionne avec les deux Rscriptet source()au sein de R. Je suggère de le faire normalizePath()sur les deux versions, afin qu'il donne le chemin complet dans les deux cas.
wch

1
C'est la seule chose qui a fonctionné. Remarque, pour que cela fonctionne, library(base)il m'a fallu un certain temps pour comprendre cela lol
O.rka

2
vous, monsieur, obtenez mon vote, car c'est la solution qui a fonctionné pour moi
Vince W.

1
Si cela aide quelqu'un, pour le poste d' origine, cela signifierait source(file.path(dirname(thisFile()), "other.R"))en foo.R. Cela fonctionne pour moi.
Kim

Un problème. Supposons dans RStudio I source main.Rquelles sources helper.Rquels appels thisFile(). Il récupérera le chemin de main.Rau lieu de helper.R. Des conseils ici?
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)]])

Ne me demandez pas comment ça marche, car j'ai oublié: /


2
Dans quel contexte cela fonctionne-t-il? print (sys.frames ()) devient NULL lorsque je l'exécute.
Suppressingfire

1
@Suppressingfire: sys.framesrenvoie les environnements de la pile d'appels, donc cela n'a vraiment de sens que lorsqu'il est appelé à partir d'une fonction. Essayez, par exemple, foo <- function() {bar <- function() print(sys.frames()); bar()}; foo(). Je ne peux pas comprendre le code de @ hadley car les environnements n'ont pas de ofilemembre.
Richie Cotton

1
Vous devez vous procurer le fichier - c'est-à-dire que si j'enregistre ce code puis l'exécute source("~/code/test.r"), PATHsera réglé sur ~/desktop. Si vous l'évaluez simplement au niveau supérieur, il renverra NULL.
hadley

4
Cela ne répond pas à ma question. J'ai besoin de trouver automatiquement le fichier "other.R". x$ofilen'est pas défini, il frame_filesest donc vide.
Frank

@hadley, code très utile. J'ai pu généraliser la fonction utilitaire "recharger le script actuel" que j'ajoute à presque tous les scripts lorsqu'ils sont en développement actif. Reloader RScript
Sim

29

Ça marche pour moi

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

4
Cela ne fonctionne que depuis RStudio je suppose. J'essaye du terminal que je reçois Error: RStudio not running.
Ista

plus précisément, cela fonctionne, s'il est exécuté à partir d'un script R dans R studio. Même sur la console de RStudio, cela ne donnera pas le bon résultat ""dans mon cas
Kay

Cela fonctionne lors de l'exécution interactive dans Rstudio tant que vous ne modifiez pas le document mis au point . Si vous soumettez des lignes à exécuter, puis basculez vers un autre document pendant leur exécution, le chemin d'accès à l'autre document sera renvoyé.
Patrick il y a

26

La réponse de rakensi de Obtenir le chemin d'un script R est à mon humble avis le plus correct et le plus brillant. Pourtant, c'est toujours un hack intégrant une fonction factice. Je le cite ici, afin qu'il soit plus facile à trouver par les autres.

sourceDir <- getSrcDirectory (fonction (factice) {factice})

Cela donne le répertoire du fichier où l'instruction a été placée (où la fonction factice est définie). Il peut ensuite être utilisé pour définir le répertoire de travail et utiliser des chemins relatifs, par exemple

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

ou pour créer des chemins absolus

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

1
Pour moi, votre solution était la meilleure. Surtout parce qu'elle pourrait être appliquée à une application Shiny et non à celle-ci.
jcarlos

1
Ici, getSrcDirectory est utils :: getSrcDirectory
RubenLaguna

5
Cela pourrait bien fonctionner sous Linux / Mac, mais cela n'a pas fonctionné pour moi dans une session RStudio interactive sous Windows. sourceDirétait vide.
Contango

1
@Contango sur un terminal interactif, il n'y a pas de chemin !!! Vous voulez le chemin d'accès à un fichier.
pommedeterresautee

1
Je reçois character(0). Suggestions?
abalter

16

Mon tout en un! (--01 / 09/2019 mis à jour pour gérer la console 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)
                }
            }
        }
    }
}

Ne fonctionne pas avec la session R interactive; Je reçois: `` `> source (" csf.R ")> csf () Erreur: RStudio ne fonctionne
pas`

C'est bien. Quelqu'un peut-il faire un paquet?
Joe Flack

Cela fonctionne lors de l'exécution interactive dans Rstudio tant que vous ne modifiez pas le document mis au point. Si vous soumettez des lignes à exécuter, puis basculez vers un autre document pendant leur exécution, le chemin d'accès à l'autre document sera renvoyé.
Patrick il y a

13

Une variante allégée de la réponse 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="/"))
}

Cela n'a pas fonctionné récursivement; le fichier que je source cherche un fichier de données (mais dans le mauvais répertoire).
The Unfun Cat

11

Cela fonctionne pour moi. Il suffit de le sortir des arguments de la ligne de commande, de supprimer le texte indésirable, de faire un nom de répertoire et d'obtenir enfin le chemin complet à partir de cela:

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

8

J'ai terminé et étendu les réponses à cette question dans une nouvelle fonction thisfile()dans rprojroot . Fonctionne également pour tricoter avec knitr.


6

J'ai aimé la solution de steamer25 car elle semble la plus robuste pour mes besoins. Cependant, lors du débogage dans RStudio (dans Windows), le chemin ne serait pas correctement défini. La raison étant que si un point d'arrêt est défini dans RStudio, le sourcing du fichier utilise une autre commande "debug source" qui définit le chemin du script un peu différemment. Voici la version finale que j'utilise actuellement et qui explique ce comportement alternatif dans RStudio lors du débogage:

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

source dans Rstudio m'a donné des informations, mais debugSource a donné fileName pour que votre solution fonctionne bien mais les commentaires de code ne sont pas tout à fait exacts dans mon cas
Mark Adamson

6

J'ai essayé presque tout de cette question, obtenir le chemin d'un script R , obtenir le chemin du script actuel , trouver l'emplacement du fichier .R actuel et la commande R pour définir le répertoire de travail à l'emplacement du fichier source dans Rstudio , mais à la fin je me suis retrouvé manuellement parcourant la table CRAN et trouvé

scriptName bibliothèque

qui fournit une current_filename()fonction, qui renvoie le chemin d'accès complet du script lors de l'approvisionnement dans RStudio et également lors de l'appel via un exécutable R ou RScript.


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

3

J'ai également eu ce problème et aucune des solutions ci-dessus n'a fonctionné pour moi. Peut-être avec lesource ou des choses comme ça, mais ce n'était pas assez clair.

J'ai trouvé cette solution, élégante pour moi:

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

L'important est fileSnapshot()que cela vous donne beaucoup d'informations sur un fichier. Il renvoie une liste de 8 éléments. Lorsque vous choisissez pathcomme élément de liste, le chemin d'accès est renvoyé avec\\ comme séparateur, donc le reste du code est juste pour changer cela.

J'espère que ça aide.


1
Cela n'a pas fonctionné pour moi sur une machine Linux; au lieu de renvoyer le chemin du fichier, il a retourné le répertoire dans lequel j'étais actuellement. J'ai créé un script de test appelé TEST.R avec une ligne de code: print (fileSnapshot () $ path) Je l'ai enregistré dans ce dossier: / opt / home / boops / Desktop / Testfolder / TEST.RI a ensuite accédé à mon bureau et essayé d'exécuter le fichier: boops @ linuxserver: ~ / Desktop $ Rscript /opt/home/boops/Desktop/Testfolder/TEST.R [1 ] "/ opt / home / boops / Desktop"
Boops Boops

Ça n'a pas marché pour moi non plus. Renvoie la même chose que «ici ()» lors de l'utilisation de la bibliothèque «ici». Il a renvoyé le chemin vers mon projet R actuellement ouvert, mais pas le fichier lui-même en cours d'exécution.
Joe Flack

2

Vous pouvez encapsuler le script r dans un script bash et récupérer le chemin du script en tant que variable bash comme ceci:

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

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

     EOF

3
Cela nécessite que vous ayez le chemin du script. Il ne vous permet pas de créer un script R vraiment portable pouvant s'exécuter de n'importe où.
Etienne Low-Décarie

@ EtienneLow-Décarie Il ne nécessite pas le chemin du script, il l'obtient de bash. Le principal problème est que ce n'est pas un moyen fiable d'obtenir le chemin. Quelque chose comme ça est préférable, comme dans stackoverflow.com/questions/59895/… path_to_script = "$ (cd" $ (dirname "$ {BASH_SOURCE [0]}") "&& pwd)"
John Haberstroh

2

J'aime cette approche:

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

2

Je viens de résoudre ça moi-même. Pour garantir la portabilité de votre script, commencez toujours par:

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

Cela fonctionne parce que "." traduit comme la commande Unix $ PWD. L'affectation de cette chaîne à un objet caractère vous permet ensuite d'insérer cet objet caractère dans setwd () et Presto votre code s'exécutera toujours avec son répertoire actuel comme répertoire de travail, quelle que soit la machine sur laquelle il se trouve ou où dans la structure de fichiers où il se trouve. situé. (Bonus supplémentaire: l'objet wd peut être utilisé avec file.path () (c'est-à-dire file.path (wd, "output_directory") pour permettre la création d'un répertoire de sortie standard quel que soit le chemin de fichier menant à votre répertoire nommé. Cela vous oblige à créer le nouveau répertoire avant de le référencer de cette façon, mais cela aussi peut être aidé avec l'objet wd.

Alternativement, le code suivant effectue exactement la même chose:

wd <- getwd()
setwd(wd)

ou, si vous n'avez pas besoin du chemin du fichier dans un objet, vous pouvez simplement:

setwd(".")

11
Nan. Cela trouve le répertoire du processus, pas le fichier lui-même.
user1071847

Cela a fonctionné pour moi dans Windows avec RStudio en mode interactif.
Contango

2

Notez que le package getopt fournit la get_Rscript_filenamefonction, qui utilise simplement la même solution présentée ici, mais est déjà écrite pour vous dans un module R standard, vous n'avez donc pas à copier et coller la fonction "get script path" dans chaque script vous écrivez.


Il retourne toujours NA, même si je crée un script qui imprime sa sortie puis appelle le script par exemple avecR -e "library(getopt); testscript.R"
bokov

1
Comme le nom de la fonction l'indique, vous devez exécuter votre script à l'aide de Rscript.
Ryan C. Thompson

Ah, oups. Merci.
bokov

1

Voir findSourceTraceback()le package R.utils , qui

Recherche tous les objets 'srcfile' générés par source () dans tous les frames d'appel. Cela permet de savoir quels fichiers sont actuellement scriptés par source ().


1

J'ai eu des problèmes avec les implémentations ci-dessus car mon script est exploité à partir d'un répertoire de liens symboliques, ou du moins c'est pourquoi je pense que les solutions ci-dessus n'ont pas fonctionné pour moi. Dans le sens de la réponse de @ ennuikiller, j'ai enveloppé mon Rscript dans bash. J'ai défini la variable de chemin à l'aide de pwd -P, qui résout les structures de répertoires à liens symboliques. Passez ensuite le chemin dans le 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

J'utiliserais une variante de l'approche de @ steamer25. Le fait est que je préfère obtenir le dernier script source même lorsque ma session a été lancée via Rscript. L'extrait suivant, lorsqu'il est inclus dans un fichier, fournira une variable thisScriptcontenant le chemin normalisé du script. J'avoue l'utilisation (ab) de source'ing, donc parfois j'invoque Rscript et le script fourni dans l' --fileargument sources un autre script qui en source un autre ... Un jour, j'investirai pour transformer mon code en désordre en un package.

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% des cas que vous pourriez simplement utiliser:

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

Cela ne fonctionnera pas pour les appels fous où le script n'est pas le premier argument, c'est-à-dire source(some args, file="myscript"). Utilisez @ hadley dans ces cas fantaisistes.


Pas depuis RStudio, cependant, sauf lors de l'approvisionnement
nJGL

1

L'approche de Steamer25 fonctionne, mais uniquement s'il n'y a pas d'espace dans le chemin. Sur macOS au moins, le cmdArgs[match]retourne quelque chose comme /base/some~+~dir~+~with~+~whitespace/pour/base/some\ dir\ with\ whitespace/ .

J'ai travaillé autour de cela en remplaçant le "~ + ~" par un simple espace blanc avant de le retourner.

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

De toute évidence, vous pouvez toujours étendre le bloc else comme l'a fait aprstar.


1

Si plutôt que le script, foo.Rconnaissant son emplacement de chemin, si vous pouvez changer votre code pour toujours référencer tous sourceles chemins d'un commun rootalors ceux-ci peuvent être d'une grande aide:

Donné

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

Cela fonctionnera

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

Voir https://rprojroot.r-lib.org/ pour savoir comment définir les racines du projet.


Pour moi, le package ici fait exactement le travail et semble être une solution 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='/'))

J'obtiens toujours l'erreur "Erreur dans sys.frame (1): pas autant de frames sur la pile"
Michael Barton

0

Étonnant, il n'y a pas de structure de type «$ 0» dans R! Vous pouvez le faire avec un appel system () à un script bash écrit en R:

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

Ensuite, divisez simplement le nom scriptpath.sh pour other.R

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

Je reçois un message d'erreurreadLink: illegal option -- e usage: readLink [-FlLnqrsx] [-f format] [-t timefmt] [file ...]
altabq

0

En regardant la pile d'appels, nous pouvons obtenir le chemin de fichier de chaque script en cours d'exécution, les deux plus utiles seront probablement soit le script en cours d'exécution, soit le premier script à trouver (entrée).

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) ))()
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.