Determinar o caminho do script em execução


255

Eu tenho um script chamado foo.Rque inclui outro script other.R, que está no mesmo diretório:

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

Mas quero Rdescobrir que, other.Rindependentemente do diretório de trabalho atual.

Em outras palavras, foo.Rprecisa conhecer seu próprio caminho. Como eu posso fazer isso?


2
Não :( Eu não vi qualquer solução que realmente funciona Além da solução para apenas passar o diretório ou usar uma variável de ambiente..
Frank

3
Seria incrível tornar os scripts totalmente portáteis e executáveis, até mesmo por neofitas!
Etienne Low-Décarie

4
Parece que todas as respostas exigem que você insira o caminho em algum momento (pelo menos na origem do arquivo)! Seria ótimo se você pudesse enviar a alguém uma pasta compactada e a execução de qualquer arquivo de script R dentro dessa pasta fosse lida e salva nessa pasta.
Etienne Low-Décarie

10
esta única questão poderia realmente se tornar te razão pela qual eu poderia completamente mudar para Python
Giacomo

5
@giac_man, sinto R está cheio de centenas de problemas pequenos como este que se somam para tornar muito difícil de trabalhar.
Michael Barton

Respostas:


102

Aqui há uma solução simples para o problema. Este comando:

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

retorna o caminho do arquivo de script atual. Funciona depois que o script foi salvo.


4
Isso não funciona para mim. Eu corro R no Windows. Qualquer ideia?
precisa saber é o seguinte

4
Obtive o mesmo erro, com um script salvo e recém-instalado e execute o R 3.2.0 no Windows ... #
2177 RalfB

27
Este erro ocorre quando você tenta executar dirname(sys.frame(1)$ofile)diretamente do Rstudio. Funciona bem quando o script é executado usando source ("other.R") e dirname(sys.frame(1)$ofile)está dentro "other.R".
Murta

4
Eu recebi o erro 'não há muitos quadros na pilha' ao chamar como um script com rscript.exe, ou seja, não usar source (). então eu tive que passar a usar a solução de Suppressingfire abaixo
Mark Adamson

3
Eu gel NULLquando este é colocado em server.R ao usar brilhante
Paul

75

Você pode usar a commandArgsfunção para obter todas as opções que foram passadas pelo Rscript para o intérprete R real e procurá-las --file=. Se seu script foi iniciado a partir do caminho ou se foi iniciado com um caminho completo, o script.nameabaixo começará com a '/'. Caso contrário, ele deve ser relativo ao cwde você pode concaturar os dois caminhos para obter o caminho completo.

Editar: parece que você precisaria apenas do item script.nameacima e retira o componente final do caminho. Eu removi a cwd()amostra desnecessária, limpei o script principal e postei o meu other.R. Apenas salve esse script e o other.Rscript no mesmo diretório chmod +x, e execute o 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)

outro.R :

print("hello")

saída :

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"

É isso que acredito que dehmann está procurando.


O que há com o downmod?
Suppressingfire

2
Eu diminuí o zoom porque sua técnica não funciona sourcecomo eu pensava que o OP queria - mas talvez eu tenha interpretado mal o requisito dele. Mas eu não posso un-downmod :( Desculpe!
Hadley

Mas, na verdade, ele funciona bem com a fonte! Apenas fonte (other.name) e funciona corretamente.
Suppressingfire

3
Para concatenação caminho, melhor usarother.name <- file.path(script.basename, "other.R")
Jason

1
Quando tento executar o commandArgs(trailingOnly = FALSE)server.R em um aplicativo brilhante, recebo [1] "RStudio" "--interactive". Nenhuma informação sobre o diretório do qual foi chamado.
Paul

57

Eu não conseguia que a solução do Suppressingfire funcionasse quando 'originava' do console R.
Não consegui que a solução da hadley funcionasse ao usar o Rscript.

Melhor dos dois 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))
        }
}

6
Eu gosto disso porque ele trabalha com ambos Rscripte source()dentro da R. eu sugeriria fazer normalizePath()nas duas versões, para que ele dê o caminho completo nos dois casos.
wch

1
Esta é a única coisa que funcionou. Note, para que este trabalho library(base)me levou um tempo para descobrir isso lol
O.rka

2
o senhor o meu voto, porque esta é a solução que funcionou para mim
Vince W.

1
Se isso ajuda qualquer um, para o post original, isso significaria source(file.path(dirname(thisFile()), "other.R"))em foo.R. Isso funciona para mim.
Kim

Uma questão. Suponha que no RStudio eu main.Ridentifique helper.Rquais fontes que chama thisFile(). Ele buscará o caminho em main.Rvez de helper.R. Alguma dica aqui?
Wassadamo 29/05/19

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

Não me pergunte como funciona, porque eu esqueci: /


2
Em que contexto isso funciona? print (sys.frames ()) aparece NULL quando eu o executo.
Suppressingfire

1
@Suppressingfire: sys.framesretorna os ambientes da pilha de chamadas, portanto só faz sentido quando chamado de uma função. Tente, por exemplo foo <- function() {bar <- function() print(sys.frames()); bar()}; foo(),. Não consigo descobrir o código do @ hadley porque os ambientes não têm um ofilemembro.
Richie Cotton

1
Você precisa originar o arquivo - ou seja, se eu salvar esse código e executar source("~/code/test.r"), PATHserá definido como ~/desktop. Se você apenas avaliar no nível superior, ele retornará NULL.
30411 hadley

4
Isso não responde à minha pergunta. Preciso encontrar automaticamente o arquivo "other.R". x$ofileestá indefinido e frame_filesvazio.
Frank

@hadley, código muito útil. Consegui generalizar a função de utilitário "reload current script" que adiciono a quase todos os scripts quando eles estão em desenvolvimento ativo. Reloader do RScript
Sim

29

Isso funciona para mim

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

4
Isso só funciona de dentro do RStudio, eu acho. Tentando no terminal que recebo Error: RStudio not running.
Ista

mais especificamente, funciona, se executado a partir de um script R no R. studio. Mesmo no console do RStudio não dará o resultado certo ""no meu caso
Kay

Isso funciona durante a execução interativa no Rstudio , desde que você não altere o documento em foco . Se você enviar linhas para execução e depois alternar para outro documento enquanto elas são executadas, o caminho para o outro documento será retornado.
Patrick

26

A resposta de rakensi de Como localizar um script R é o IMHO mais correto e realmente brilhante. No entanto, ainda é um hack incorporando uma função fictícia. Estou citando aqui, para que seja mais fácil encontrar outras pessoas.

sourceDir <- getSrcDirectory (função (dummy) {dummy})

Isso fornece o diretório do arquivo onde a instrução foi colocada (onde a função fictícia está definida). Em seguida, ele pode ser usado para definir o diretório de trabalho e usar caminhos relativos, por exemplo

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

ou para criar caminhos absolutos

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

1
Para mim, sua solução foi a melhor. Especialmente porque poderia ser aplicado a um aplicativo Shiny e aquele no link não.
jcarlos

1
Aqui o getSrcDirectory é utils :: getSrcDirectory
RubenLaguna 25/16

5
Isso pode funcionar bem no Linux / Mac, mas não funcionou para mim em uma sessão interativa do RStudio no Windows. sourceDirestava em branco.
Contango 8/08/17

1
@ Contango em um terminal interativo, não há caminho !!! Você deseja o caminho para um arquivo.
Pommedeterresautee

1
Estou recebendo character(0). Sugestões?
abalter

16

Meu tudo em um! (- 01/09/2019 atualizado para lidar com o 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)
                }
            }
        }
    }
}

Não funciona com a sessão interativa do R; Estou recebendo: ``>> source ("csf.R")> csf () Erro: o RStudio não está executando `` ``
ManicMailman

Isso é ótimo. Alguém pode fazer um pacote?
Joe Flack

Isso funciona durante a execução interativa no Rstudio, desde que você não altere o documento em foco. Se você enviar linhas para execução e depois alternar para outro documento enquanto elas são executadas, o caminho para o outro documento será retornado.
Patrick

13

Uma variante reduzida da resposta do Supressingfire:

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

Isso não funcionou recursivamente; o arquivo que eu procuro procura um arquivo de dados (mas no diretório errado).
The Unfun Cat

11

Isso funciona para mim. Apenas o apaga dos argumentos da linha de comando, retira o texto indesejado, cria um nome de diretório e, finalmente, obtém o caminho completo disso:

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

8

Eu encerrei e estendi as respostas para esta pergunta em uma nova função thisfile()no rprojroot . Também funciona para tricô knitr.


6

Gostei da solução do steamer25, pois parece a mais robusta para meus propósitos. No entanto, ao depurar no RStudio (no Windows), o caminho não seria definido corretamente. A razão é que, se um ponto de interrupção for definido no RStudio, a fonte do arquivo usará um comando "debug source" alternativo, que define o caminho do script de maneira um pouco diferente. Aqui está a versão final que estou usando no momento e que explica esse comportamento alternativo no RStudio ao 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))
        }
    }
}

fonte em rstudio deu oFile para mim, mas debugSource deu fileName para que a sua solução funciona bem, mas os comentários de código não estão muito bem no meu caso
Mark Adamson

6

Eu tentei quase tudo nesta questão, Obtendo o caminho de um script R , Obter o caminho do script atual , Encontrar o local do arquivo .R atual e o comando R para definir o diretório de trabalho no local do arquivo de origem no Rstudio , mas no final me encontrei manualmente navegando na tabela CRAN e encontrado

scriptName biblioteca

que fornece a current_filename()função, que retorna o caminho completo adequado do script ao buscar no RStudio e também ao chamar via executável R ou RScript.


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

3

Eu também tive esse problema e nenhuma das soluções acima funcionou para mim. Talvez com osource ou coisas assim, mas não estava claro o suficiente.

Achei isso, para mim, solução elegante:

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

O importante é fileSnapshot()que você fornece muitas informações sobre um arquivo. Retorna uma lista de 8 elementos. Quando você escolhe pathcomo o elemento da lista, o caminho é retornado com\\ como separador; portanto, o restante do código é apenas para alterar isso.

Eu espero que isso ajude.


1
Isso não funcionou para mim em uma máquina Linux; em vez de retornar o caminho do arquivo, ele retornou o diretório em que eu estava localizado. Criei um script de teste chamado TEST.R com uma linha de código: print (fileSnapshot () $ path) Salvei-o nesta pasta: / opt / home / boops / Desktop / Testfolder / TEST.RI navegou até minha área de trabalho e tentou executar o arquivo: boops @ linuxserver: ~ / Desktop $ Rscript /opt/home/boops/Desktop/Testfolder/TEST.R [1 ] "/ opt / home / boops / Desktop"
Boops Boops em

Também não funcionou para mim. Retorna o mesmo que 'here ()' ao usar a biblioteca 'here'. Ele retornou o caminho para o meu projeto R atualmente aberto, mas ele não está sendo executado.
Joe Flack

2

Você pode agrupar o script r em um script bash e recuperar o caminho do script como uma variável bash da seguinte forma:

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

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

     EOF

3
Isso requer que você tenha o caminho do script. Ele não permite que você crie um script R verdadeiramente portátil que possa ser executado de qualquer lugar.
Etienne Low-Décarie

@ EtienneLow-Décarie Não requer o caminho do script, ele o obtém do bash. A questão principal é que não é uma maneira confiável de seguir o caminho. Algo assim é preferido, como em stackoverflow.com/questions/59895/… path_to_script = "$ (cd" $ (nome do diretório "$ {BASH_SOURCE [0]}") "&& pwd)"
John Haberstroh em

2

Eu gosto desta abordagem:

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

2

Eu mesmo resolvi isso sozinho. Para garantir a portabilidade do seu script, inicie-o sempre com:

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

Funciona porque "." traduz como o comando Unix $ PWD. Atribuir essa sequência a um objeto de caractere permite inserir esse objeto de caractere em setwd () e Presto seu código sempre será executado com o diretório atual como diretório de trabalho, independentemente da máquina em que está ou na estrutura do arquivo. localizado. (Bônus extra: o objeto wd pode ser usado com file.path () (ou seja, file.path (wd, "output_directory") para permitir a criação de um diretório de saída padrão, independentemente do caminho do arquivo que leva ao diretório nomeado. Isso exige que você crie o novo diretório antes de referenciá-lo dessa maneira, mas que também pode ser auxiliado com o objeto wd.

Como alternativa, o código a seguir executa exatamente a mesma coisa:

wd <- getwd()
setwd(wd)

ou, se você não precisar do caminho do arquivo em um objeto, pode simplesmente:

setwd(".")

11
Não. Isso localiza o diretório do processo, não o arquivo em si.
usar o seguinte comando

Isso funcionou para mim no Windows com o RStudio no modo interativo.
Contango 8/08

2

Observe que o pacote getopt fornece a get_Rscript_filenamefunção, que apenas usa a mesma solução apresentada aqui, mas já está escrita para você em um módulo R padrão, portanto, você não precisa copiar e colar a função "get script path" em todos os scripts você escreve.


Ele sempre retorna NA, mesmo se eu criar um script que imprime a sua saída e, em seguida, chamar o script por exemplo, comR -e "library(getopt); testscript.R"
Bokov

1
Como o nome da função implica, você precisa executar seu script usando Rscript.
Ryan C. Thompson

Ah, opa. Obrigado.
Bokov

1

Veja findSourceTraceback()o pacote R.utils , que

Localiza todos os objetos 'srcfile' gerados pela origem () em todos os quadros de chamada. Isso torna possível descobrir quais arquivos estão atualmente em script por source ().


1

Eu tive problemas com as implementações acima, pois meu script é operado a partir de um diretório com link simbólico, ou pelo menos é por isso que acho que as soluções acima não funcionaram para mim. Na linha da resposta do @ ennuikiller, envolvi meu Rscript no bash. Defino a variável de caminho usando pwd -P, que resolve as estruturas de diretório com links simbólicos. Em seguida, passe o caminho para o 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

Eu usaria uma variante da abordagem do @ steamer25. O ponto é que eu prefiro obter o último script de origem, mesmo quando minha sessão foi iniciada pelo Rscript. O seguinte snippet, quando incluído em um arquivo, fornecerá uma variável que thisScriptcontém o caminho normalizado do script. Confesso o (ab) uso de source'ing, então, às vezes, invoco o Rscript e o script fornecido no --fileargumento origina outro script que origina outro ... Algum dia eu investirei para transformar meu código confuso em um pacote.

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% dos casos você pode simplesmente usar:

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

Não funcionará para chamadas malucas onde o script não é o primeiro argumento, ou seja source(some args, file="myscript"),. Use @ hadley's nesses casos sofisticados.


Não de dentro rstudio, embora, exceto quando sourcing
nJGL

1

A abordagem do Steamer25 funciona, mas apenas se não houver espaço em branco no caminho. No macOS, pelo menos, os cmdArgs[match]retornos são parecidos /base/some~+~dir~+~with~+~whitespace/com /base/some\ dir\ with\ whitespace/.

Eu resolvi isso substituindo o "~ + ~" por um espaço em branco simples antes de devolvê-lo.

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, você ainda pode estender o bloco else como o aprstar fez.


1

Se, em vez do script, foo.Rconhecendo a localização do caminho, se você puder alterar seu código para sempre fazer referência a todos sourceos caminhos de um comum root, pode ser uma grande ajuda:

Dado

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

Isso vai funcionar

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

Veja https://rprojroot.r-lib.org/ para saber como definir as raízes do projeto.


Para mim, o Aqui Pacote de fazer exatamente o trabalho e parece ser uma solução 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='/'))

Ainda recebo o erro "Erro no sys.frame (1): não há muitos quadros na pilha"
Michael Barton

0

Incrível, não existe uma estrutura do tipo '$ 0' no R! Você pode fazer isso com uma chamada system () para um script bash escrito em R:

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

Em seguida, apenas divida o nome scriptpath.sh para other.R

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

Recebo uma mensagem de erroreadLink: illegal option -- e usage: readLink [-FlLnqrsx] [-f format] [-t timefmt] [file ...]
altabq

0

Observando a pilha de chamadas, podemos obter o caminho do arquivo de cada script sendo executado, os dois mais úteis provavelmente serão o script atualmente em execução ou o primeiro script a ser originado (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) ))()
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.