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?

Frank
fonte
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.

this.is.not.a.nick
fonte
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.

Suprimindo fogo
fonte
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))
        }
}
steamer25
fonte
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: /

Hadley
fonte
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
ColinTea
fonte
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=""))
manguito
fonte
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)
                }
            }
        }
    }
}
Jerry T
fonte
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="/"))
}
momeara
fonte
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)])))
eddi
fonte
8

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

krlmlr
fonte
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))
        }
    }
}
aprstar
fonte
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.

Bojan P.
fonte
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.

Antoine
fonte
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
ennuikiller
fonte
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)
kuna.matata
fonte
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(".")
Andrew Moffat Jr.
fonte
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.

Ryan C. Thompson
fonte
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 ().

HenrikB
fonte
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)
Luke Singham
fonte
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))
  }
})()
Ailton Andrade de Oliveira
fonte
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.

antonio
fonte
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.

Eu bola
fonte
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.

mmell
fonte
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='/'))
kinjelom
fonte
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")
bruce.moran
fonte
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) ))()
user425678
fonte