使用Shell无法从VBA内部执行R脚本

时间:2019-02-28 02:40:52

标签: r vba shell

我有一个R脚本,当我从R Studio执行它时,它可以完美运行,但是当我尝试使用调用Shell的VBA代码运行它时,该代码运行,命令窗口显示并关闭,但是不生成结果文件。它不会引发任何错误。有人可以看到问题所在吗?

这是具有Rscript.exe文件的文件夹的地址:C:\Program Files\R\R-3.4.4\bin\x64\

VBA代码:

Sub RunRScript()
    Dim shell As Object

    Dim waitTillComplete As Boolean: waitTillComplete = True
    Dim style As Integer: style = 1
    Dim errorCode As Integer
    Dim path As String
    Set shell = VBA.CreateObject("WScript.Shell")

    path = """C:\Program Files\R\R-3.4.4\bin\x64\RScript"" C:\Ibos\R\WF_Metrics\abc.R"

    errorCode = shell.Run(path, style, waitTillComplete)

End Sub

R脚本:

library(RODBC)
library(dplyr)
#library(data.table)
library(tidyr)
library(tictoc)
library(tidyverse)
library(lubridate)
library(xlsx)
library(sqldf)

#set working directory
setwd("C:/Ibos/R/WF_Metrics")


my_server="servername"
my_db="dbname"
my_username="username"
my_pwd="password"


db <- odbcDriverConnect(paste0("DRIVER={SQL Server};
                               server=",my_server,";
                               database=",my_db,";
                               uid=",my_username,";
                               pwd=",my_pwd))


sql="select * from dbo.metricsfinal"

df <- sqlQuery(db,sql)


myfile="results"
write.csv(df, file = paste0(myfile,"_test",".csv") ,row.names=FALSE)

编辑:

在奥利弗(Oliver)的回答和其他人的一些有益评论之后,我发现问题出在xlsx软件包中。现在,我需要弄清楚如何解决此问题。我更喜欢使用此程序包,而不是寻找其他程序包/选项,不胜感激。这是错误:

  

错误:“ xlsx”的包或名称空间加载失败:.onLoad失败   用于'rJava'的loadNamespace(),详细信息:调用:   dirname(this $ RuntimeLib)错误:字符向量参数   预期的执行停止

1 个答案:

答案 0 :(得分:1)

从VBA运行R是一项令人讨厌的工作。如果您想直接从VBA运行R代码,我建议您研究BERT,它是Excel的开源加载项,可让您直接从excel运行和编写R代码,从而大大调试了代码更简单。

这就是说,如果您陷在shell.run中,可以做一些事情来定位问题。

手动调试

在VBA中,设置断点或将您的路径打印到控制台。

path = """C:\Program Files\R\R-3.4.4\bin\x64\RScript"" C:\Ibos\R\WF_Metrics\abc.R"
debug.print path

打开命令提示符(按Windows按钮并输入cmd,然后按Enter start, cmd, enter。)

将代码行粘贴到命令提示符中。任何错误都将被打印到命令行中,然后您可以在脚本中找到并更正错误。

减少手动调试

现在,手动调试可能很乏味,正如我在下面指出的,它是特定于系统的。有几个选项可以使过程稍微自动化。这些包括:

  1. 将错误代码直接读入VBA,将任何输出传递到VBA。
  2. 使用BERT之类的开放源代码集成,使您可以直接在Excel中直接编写和调试代码。
  3. 使用系统错误消息来识别错误

第一个选项比较复杂,但是也非常通用且不错。我建议使用the first answer here,它提供了可以实现此方法的VBA模块的链接。但是请注意,它是一个32位模块,并且需要一些ptrsafe标记,Windows api才能在Excel的64位安装上运行。只需进行一些更改,它甚至可以用于直接读取R中的文本输出(data.frame等),而对Excel的干扰最小。

对于第二个选项,我建议查看BERT网页,该网页为使用该实现提供了很好的指南。这样做的缺点是,除了要安装R以外,任何计算机都需要安装BERT才能使excel脚本正常工作。

第三个选项是受Chip Pearson's site.启发的选项,当脚本崩溃时,它会将错误代码发送到命令行,并且Windows错误代码管理器可以解释该错误代码。这具有简单的优点,并且您会很快知道脚本“不存在”还是非R特定的类似常见错误。

使用此方法可以将R执行脚本更改为类似

Sub RunRScript()
    Dim shell As Object

    Dim waitTillComplete As Boolean: waitTillComplete = True
    Dim style As Integer: style = 1
    Dim errorCode As Integer
    Dim path As String
    Set shell = VBA.CreateObject("WScript.Shell")

    path = """C:\Program Files\R\R-3.4.4\bin\x64\RScript"" C:\Ibos\R\WF_Metrics\abc.R"

    errorCode = shell.Run(path, style, waitTillComplete)
    if errorCode <> 0 then
        errorString = GetSystemErrorMessageText(errorCode)
        Err.Raise errorCode, "Run_R_Script", errorString
    end if 
End Sub

其中GetSystemErrorMessageText(errorCode)是对下面单独模块中的函数的调用。

#If Win64 Then
    Private Declare PtrSafe Function FormatMessage Lib "kernel32" Alias "FormatMessageA" ( _
        ByVal dwFlags As Long, _
        ByVal lpSource As Any, _
        ByVal dwMessageId As Long, _
        ByVal dwLanguageId As Long, _
        ByVal lpBuffer As String, _
        ByVal nSize As Long, _
        ByRef Arguments As Long) As Long
#Else
    Private Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA" ( _
        ByVal dwFlags As Long, _
        ByVal lpSource As Any, _
        ByVal dwMessageId As Long, _
        ByVal dwLanguageId As Long, _
        ByVal lpBuffer As String, _
        ByVal nSize As Long, _
        ByRef Arguments As Long) As Long
#End If
Public Function GetSystemErrorMessageText(ErrorNumber As Long) As String
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' GetSystemErrorMessageText
    '
    ' This function gets the system error message text that corresponds
    ' to the error code parameter ErrorNumber. This value is the value returned
    ' by Err.LastDLLError or by GetLastError, or occasionally as the returned
    ' result of a Windows API function.
    '
    ' These are NOT the error numbers returned by Err.Number (for these
    ' errors, use Err.Description to get the description of the error).
    '
    ' In general, you should use Err.LastDllError rather than GetLastError
    ' because under some circumstances the value of GetLastError will be
    ' reset to 0 before the value is returned to VBA. Err.LastDllError will
    ' always reliably return the last error number raised in an API function.
    '
    ' The function returns vbNullString is an error occurred or if there is
    ' no error text for the specified error number.
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Dim ErrorText As String
    Dim TextLen As Long
    Dim FormatMessageResult As Long
    Dim LangID As Long
    ''''''''''''''''''''''''''''''''
    ' Initialize the variables
    ''''''''''''''''''''''''''''''''
    LangID = 0&   ' Default language
    ErrorText = String$(FORMAT_MESSAGE_TEXT_LEN, vbNullChar)
    TextLen = FORMAT_MESSAGE_TEXT_LEN
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' Call FormatMessage to get the text of the error message text
    ' associated with ErrorNumber.
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    FormatMessageResult = FormatMessage( _
                            dwFlags:=FORMAT_MESSAGE_FROM_SYSTEM Or _
                                     FORMAT_MESSAGE_IGNORE_INSERTS, _
                            lpSource:=0&, _
                            dwMessageId:=ErrorNumber, _
                            dwLanguageId:=LangID, _
                            lpBuffer:=ErrorText, _
                            nSize:=TextLen, _
                            Arguments:=0&)
    If FormatMessageResult = 0& Then
        ''''''''''''''''''''''''''''''''''''''''''''''''''
        ' An error occured. Display the error number, but
        ' don't call GetSystemErrorMessageText to get the
        ' text, which would likely cause the error again,
        ' getting us into a loop.
        ''''''''''''''''''''''''''''''''''''''''''''''''''
        MsgBox "An error occurred with the FormatMessage" & _
               " API function call." & vbCrLf & _
               "Error: " & CStr(Err.LastDllError) & _
               " Hex(" & Hex(Err.LastDllError) & ")."
        GetSystemErrorMessageText = "An internal system error occurred with the" & vbCrLf & _
            "FormatMessage API function: " & CStr(Err.LastDllError) & ". No futher information" & vbCrLf & _
            "is available."
        Exit Function
    End If
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' If FormatMessageResult is not zero, it is the number
    ' of characters placed in the ErrorText variable.
    ' Take the left FormatMessageResult characters and
    ' return that text.
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ErrorText = Left$(ErrorText, FormatMessageResult)
    '''''''''''''''''''''''''''''''''''''''''''''
    ' Get rid of the trailing vbCrLf, if present.
    '''''''''''''''''''''''''''''''''''''''''''''
    If Len(ErrorText) >= 2 Then
        If Right$(ErrorText, 2) = vbCrLf Then
            ErrorText = Left$(ErrorText, Len(ErrorText) - 2)
        End If
    End If
    ''''''''''''''''''''''''''''''''
    ' Return the error text as the
    ' result.
    ''''''''''''''''''''''''''''''''
    GetSystemErrorMessageText = ErrorText
End Function

贷记项转到Chip Pearson,尽管它可能不打算用于此用途。 现在执行shell.run(path)时,path是执行R脚本的命令,如果失败,则将返回错误消息。 这并不能完全删除手动调试,但是错误消息将帮助您识别R之外的错误,并经常提供有价值的错误描述,以帮助您在手动调试期间更快地识别错误。

因此,应在必要时手动调试此方法。

注意:

  1. 从命令行运行R脚本专门用于系统编码。从命令行运行时,使用“使用编码保存”和“ System.locale”保存R脚本消除了我们的大多数问题。但是,在使用UTF8字符时可能会失败,这是另一个麻烦。
  2. 第三个选项最简单,因此用途最少。 Windows系统中的错误代码仅使您一目了然,没有指定失败的行,仅调用了Invalid function或类似的代码。无法保证此错误将完全正确。但是,它确实会给您带来最常见的错误,例如错误的路径,功能根本不起作用,所调用的变量不存在等。

我希望这可以使您有所了解并帮助您找到错误。我建议在google上搜索,使用shell.run进行各种集成的主题已经过调查,如果存在更好的选择,通常建议避免使用它(由于限制)。但是,它通常是一个很好的起点。许多站点like this one展示了如何通过将输出保存到R中的文本文件并从VBA中读取来使用VBA中R中的输出。确实存在更好的选项,但这可能是最简单的方法。

由Asker更新: 经过大量调查,我意识到以下几点:

1- xlsx软件包使用rJava软件包,您需要先安装Java

2- Java的32位或64位版本会影响rJava软件包以及随后的xlsx软件包能否成功加载。如果您使用的是64位Windows操作系统,则建议安装所有内容(Excel,R,Java)的64位版本。

3-似乎默认情况下R已安装32位和64位版本,因此您必须指定要使用的版本。在RStudio上选择版本:

Tools > Global Options > R Version

默认情况下,

是32位的,并且在我安装了Java的新版本后,Library(rJava)甚至在RStudio上也会引发错误。我将其更改为64位,并且可以正常工作。

您也可以使用R脚本来执行此操作,但是,如果您从RStudion内部运行此代码,该代码将无法正常工作,因为您需要手动更改R版本并关闭RStudion并再次启动才能生效

Sys.setenv(JAVA_HOME='C:\\Program Files\\Java\\jre7') # for 64-bit version
Sys.setenv(JAVA_HOME='C:\\Program Files (x86)\\Java\\jre7') # for 32-bit version
library(rJava)

4-我注意到我的机器上同时安装了32位和64位Java。对环境变量执行JAVA_HOME后,它导致了jvm.dll missing error,因此我删除了它,一切又恢复正常了

5-我在一些帖子中看到,如果您对xlsx软件包使用另一种替代方法,则无需费劲确保所有功能都可以正常工作。