使用R读取以"smb://"开头的远程文件

23

要在R中读取文件,我通常会像以下这样做:

read.csv('/Users/myusername/myfilename.csv')

但是,我正在尝试读取位于远程服务器(Windows SMB/CIFS共享)上的文件,我通过 Finder → 前往 → 连接服务器 菜单项在我的 Mac 上访问它。

当我查看那个文件的属性时,文件路径与我所习惯的不同。它不是以 /Users/myusername/... 开头,而是 smb://server.msu.edu/.../myfilename.csv

尝试读取该文件时,我尝试了以下操作:

read.csv('smb://server.msu.edu/.../myfilename.csv')

但是,这样做没有起作用。

与通常的"文件或目录不存在"错误不同,它返回了:

smb://server.msu.edu/.../myfilename.csv 在当前工作目录中不存在

我想文件路径需要不同的格式,但我弄不清楚是什么。

如何在 R 中读取此类文件?


4
也许这个链接能提供帮助。 - Rich Scriven
1
@HongOoi 当然可以。这有助于确定您可以提供给read.csv函数的文件路径。 - Abdou
1
@Abdou 看起来 /Volumes/lastdirectoryinfilepath/filename.csv 可以工作了 - 路径中的 smb://educ-srvmedia1.campusad.msu.edu/... 部分是不必要的。 - Joshua Rosenberg
@Moody_Mudskipper 在Windows上,如果需要的话,我们可以从我们的程序中自动化该步骤system("net use /user:domain\\username \\\\host.example.com\\share") - Cy Rossignol
1
@Cy Rossignol,太棒了!我一定会把这个加入我的Rprofile中。 - moodymudskipper
显示剩余7条评论
5个回答

21

说明

smb://educ-srvmedia1.campusad.msu.edu/...实际上是一个URL而不是文件路径。

让我们来分解一下:

smb://表示使用服务器信息块协议(文件共享)。

educ-srvmedia1.campusad.msu.edu是服务器的名称。

/.../myfilename.csv是远程服务器上的文件共享/路径。

您可以在OSX上使用Finder导航到此目录,因为它内置了对SMB协议的支持。Finder使用URL连接到远程服务并允许您浏览文件。

然而,R不理解SMB协议,因此无法正确解释文件路径。

R函数read.csv()在内部使用file(),请参见https://stat.ethz.ch/R-manual/R-devel/library/base/html/connections.html

url和file支持URL方案file://、http://、https://和ftp://。

因此,R返回“无法定位文件”的消息,因为找不到文件,原因是协议不受支持。是的,有点令人困惑。

解决方法

您需要将文件共享挂载到本地文件系统上。

所有这意味着操作系统将在幕后处理SMB协议的详细信息,并将文件共享呈现为本地目录。

这将允许R(和其他程序)将远程文件视为本地文件。

此讨论显示了一些执行此操作的选项。

例如:

# need to create /LocalFolder first
mount -t cifs //username:password@hostname/sharename /LocalFolder

然后在R中:

read.csv('/LocalFolder/myfilename.csv')

额外信息

对于Windows用户来说,可以使用UNC路径更容易地完成这个任务。
如何在R中从指定的UNC目录读取文件?


@andrew,你认为这个回答解决了问题吗? - Joshua Rosenberg
@stacksonstacks 挂载光盘与我通过“Finder”->“前往”->“连接服务器”时的操作是否相似或不同? - Joshua Rosenberg
1
@JoshuaRosenberg 它很相似,其实就是 Finder 在幕后所做的事情。 - stacksonstacks
@Joshua,mount 命令挂载系统级文件系统,而“连接到服务器”通过FUSE为特定用户挂载文件系统。 - Cy Rossignol

12

简而言之

这是一种便携式的方法,使用cURL并且不需要挂载远程文件系统:

> install.packages("curl")
> require("curl")
> handle <- new_handle()
> handle_setopt(handle, username = "domain\\username")
> handle_setopt(handle, password = "secret") # If needed
> request <- curl_fetch_memory("smb://host.example.com/share/file.txt", handle = handle)
> contents <- rawToChar(request$content)

如果我们需要像问题中那样读取CSV内容,可以通过另一个函数对文件进行流式传输:
> stream <- curl("smb://host.example.com/share/file.txt", handle = handle)
> contents <- read.csv(stream)

让我们来看一种更健壮的通过 URL访问远程文件的方法,除了其他答案中描述的挂载远程文件系统的方法。不幸的是,我有点晚了,但我希望这对未来的读者有所帮助。
在某些情况下,我们可能没有挂载文件系统所需的特权(这需要许多系统上的管理员或root访问权限),或者我们仅仅是不想挂载整个文件系统来读取单个文件。我们将使用cURL库来读取文件。这种方法提高了程序的灵活性和可移植性,因为我们不需要依赖于外部挂载的文件系统的存在。我们将研究两种不同的方式:通过一个system()调用,以及使用提供cURL API的包。
一些背景知识:对于那些不熟悉它的人来说,cURL提供了用于在各种协议上传输数据的工具。自7.40版本以来,cURL支持SMB/CIFS协议,通常用于Windows文件共享服务。cURL包括一个命令行工具,我们可以使用它来获取文件的内容:
$ curl -u 'domain\username' 'smb://host.example.com/share/file.txt'

上述命令将以指定用户身份在域上认证,从远程服务器host.example.com读取并输出(到STDOUT)file.txt的内容。如果需要,该命令会提示我们输入密码。如果我们的网络不使用域名,可以从用户名中删除域部分。
系统调用
我们可以通过使用system()函数在R中实现相同的功能:
system("curl -u 'domain\\username' 'smb://host.example.com/share/file.txt'")

注意domain\\username中的双反斜杠。这是为了转义反斜杠字符,使R不会将其解释为字符串中的转义字符。我们可以通过将system()函数的intern参数设置为TRUE,将命令输出的文件内容捕获到变量中:
contents <- system("curl -u 'domain\\username' 'smb://host.example.com/share/file.txt'", intern = TRUE)

或者,您可以调用system2(),这样可以更安全地引用命令参数,并更好地处理跨平台的进程重定向:

contents <- system2('curl', c("-u", "domain\\\\username", "smb://host.example.com/share/file.txt"), stdout = TRUE)
curl 命令如果远程服务器要求密码,仍会提示我们输入。虽然我们可以使用 -u 'domain\\username:password' 指定密码来避免提示,但这样做会在命令字符串中暴露明文密码。为了更安全的方法,请阅读下面描述包的使用部分。
我们还可以向 curl 命令添加 -s--silent 标志以抑制进度状态输出。请注意,这样做也将隐藏错误消息,因此我们可能还想添加 -S (--show-error)。变量 contents 将包含文件行的向量,类似于 readLines("file.txt") 返回的值,我们可以使用 paste(contents, collapse = "\n") 将其压缩回去。 cURL API 虽然这一切都可以正常工作,但是我们可以通过使用专用的 cURL 库来改进这种方法。这个 curl package 提供了 R 绑定到 libcurl,这样我们就可以直接在程序中使用 cURL API。首先我们需要安装该软件包:
install.packages("curl")
require("curl")

(Linux用户需要安装libcurl开发文件。)

然后,我们可以使用curl_fetch_memory()函数将远程文件读入变量中:

handle <- new_handle()
handle_setopt(handle, username = "domain\\username")
handle_setopt(handle, password = "secret") # If needed
request <- curl_fetch_memory("smb://host.example.com/share/file.txt", handle = handle)
content <- rawToChar(request$content)

首先,我们创建一个 "handle" 来配置请求,通过设置所需的任何身份验证选项。然后,我们执行请求并将文件内容分配给变量。如示,如果需要,请设置 "password" CURLOPT。
要处理类似于使用 "read.csv()" 的远程文件,我们需要创建一个流连接。 "curl()" 函数创建一个连接对象,我们可以使用它来通过任何支持标准 "url()" 函数返回的参数的函数来流式传输文件内容。例如,这是一种以 CSV 格式读取远程文件的方法,就像在问题中一样:
handle = new_handle()
...
stream <- curl("smb://host.example.com/share/file.txt", handle = handle)
contents <- read.csv(stream)

当然,上述描述的概念适用于通过cURL支持的任何协议获取内容或响应正文,而不仅仅是SMB/CIFS。如果需要,我们还可以使用这些工具将文件下载到文件系统中,而不仅仅是将内容读入内存中。

1
很棒的技巧,Cy!感谢您的贡献,我从未知道 curl 可以支持这个。 - r2evans
很好地使用了 curl。值得注意的是,在最近的 Samba 版本中,这将无法与默认服务器配置一起使用,因为 curl 仅支持 SMB1 协议和 NTLMv1 认证方法,并且在 Samba 中默认禁用它们(最近的版本使用 SMB2/3 和 NTLMv2)。必须更改 Samba 服务器配置以接受旧协议和身份验证方法(server min protocol = NT1ntlm auth = yes),但这会带来安全风险,大多数系统管理员不会允许这样做。 - MalditoBarbudo

7
以下是我经常使用的一种从SMB网络驱动器读取数据的方法。 在下面的代码中,我使用了R system函数来在R内部执行所有操作,但您也可以使用OSX命令行或在Finder中使用Command-K(连接到服务器)来挂载驱动器:
如果您还没有,请在本地驱动器上创建一个目录,其中将存储共享内容(这不是必需的,因为您可以将驱动器挂载到现有位置)。
system("mkdir /Users/eipi10/temp_share/")

或者

dir.create("/Users/eipi10/temp_share/")

将网络驱动器挂载到刚创建的文件夹中。在下面的代码中,//username@domain.address.edu/home/u/eipi10 是您的用户名和SMB共享地址。
system("mount_smbfs //username@domain.address.edu/home/u/eipi10 /Users/eipi10/temp_share")

如果有密码验证,则密码也可以包含在内:

system("mount_smbfs //username:password@domain.address.edu/home/u/eipi10 /Users/eipi10/temp_share")

读取数据:

dat = read.csv("/Users/eipi10/temp_share/fileToRead.csv")

你可以在R内编写程序来选择要读取的文件:

data.list = lapply(list.files(pattern="csv$", "/Users/eipi10/temp_share/", full.names=TRUE), read.csv)

1

SMB是Windows网络文件夹协议。

类似的情况包括sftp:// URL,例如。

您可以选择:

  1. 在操作系统中挂载文件夹,并使用常规路径访问它,
  2. 使用虚拟文件系统库,如Linux上的GVFS/GIO。也许存在一些R包装器可供使用。

请参考 https://gist.github.com/natritmeyer/6621231 了解如何使用选项1。 - mnagel

0
在我看来,有两种方法可以实现你的目标。
  • 第一种方法是使用fstab将远程文件夹明确添加为本地磁盘

  • 第二种方法是在需要时将远程文件夹临时挂载为文件夹


以下,我将解释第二种方法如何实现:
  • 创建本地目录:

    mkdir <mountdirectory>

  • 使用以下命令将远程目录挂载到本地:

    sshfs <remoteserverip>:<remotedirpath> <mountdirectory> 用于SSH

    或者(首先安装cifs util:sudo apt-get install cifs-utils

    mount -t cifs -o username=<USERNAME>,password=<PASSWD> //<remoteserverip>/<remotedirpath> <mountdirectory> 用于SMB

  • 使用本地文件完成任务

  • 最后,使用以下命令取消挂载:

    fusermount -u <mountdirectory>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接