在Ruby中确定文件类型

81

如何可靠地确定文件类型?不能使用文件扩展名分析。是否有类似于UNIX file(1)命令的Ruby工具可用?

这涉及MIME或内容类型,而不是文件系统分类,例如目录、文件或套接字。

13个回答

58

有一个 Ruby 绑定到 libmagic,可以满足你的需求。它作为一个名为 ruby-filemagic 的宝石(gem)提供:

gem install ruby-filemagic

需要安装 libmagic-dev 库。

文档似乎有点简单,但这可以让您开始:

$ irb 
irb(main):001:0> require 'filemagic' 
=> true
irb(main):002:0> fm = FileMagic.new
=> #<FileMagic:0x7fd4afb0>
irb(main):003:0> fm.file('foo.zip') 
=> "Zip archive data, at least v2.0 to extract"
irb(main):004:0> 

根据 http://grub.ath.cx/filemagic/CHANGELOG 记录,这个 gem 似乎没有得到积极维护。 - Lars Haugseth
23
很高兴地告诉您,这个宝石(指的是一个开源项目)再次得到积极维护。链接为 https://github.com/blackwinter/ruby-filemagic。 - Martin Carpenter
也适用于Windows操作系统。 - chris finne
3
这个宝石似乎已经停止维护了。在 Github 上,它被标记为“未维护”和“可接手”。(https://github.com/blackwinter/ruby-filemagic) - tanius

35

如果您使用的是Unix机器,请尝试以下操作:

mimetype = `file -Ib #{path}`.gsub(/\n/,"")

我不知道有没有任何纯 Ruby 的解决方案能像 'file' 一样可靠地工作。

编辑以添加:根据您运行的操作系统,您可能需要使用 'i' 而不是 'I' 以使文件返回 MIME 类型。


18
为了防止恶意的黑客攻击,建议尝试使用popen:IO.popen(["file", "--brief", "--mime-type", path], in: :close, err: :close).read.chomp。该命令可用于获取文件的MIME类型,使用时需指定相应的路径。 - sj26
是的,使用这个或者 cocaine 宝石包。 - maletor
8
每次我调用popen,都会得到一个僵尸进程,因为IO对象没有被关闭。为了解决这个问题,使用一个代码块: IO.popen(["file", "--brief", "--mime-type", path], in: :close, err: :close) { |io| io.read.chomp } - Andrew
@sj26 你所说的“nasty hackery”是什么意思?反引号是否被认为是有害的? - Pete
1
@Pete 将潜在的用户提供的内容插入到类似反引号的命令字符串中是一种潜在的安全漏洞。使用带有参数数组的 popen 可以防止此类攻击。 :-) - sj26
1
关于僵尸的观点非常好!IO.popen(["file", "--brief", "--mime-type", path], &:read).chomp也可以使用。 - sj26

14

我发现使用 shelling out 是最可靠的。为了保证在 Mac OS X 和 Ubuntu Linux 上兼容性,我使用了以下命令:

file --mime -b myvideo.mp4
视频/mp4; 字符集=binary

Ubuntu 还可以打印视频编解码器信息,非常酷:

file -b myvideo.mp4
ISO 媒体,MPEG v4 系统,版本 2


6
网站使用应该使用命令 file -b --mime-type myvideo.mp4 来确定视频文件的类型。 - Yam Marcovic

10

根据文件的魔数头,您可以使用这种可靠的方法:

def get_image_extension(local_file_path)
  png = Regexp.new("\x89PNG".force_encoding("binary"))
  jpg = Regexp.new("\xff\xd8\xff\xe0\x00\x10JFIF".force_encoding("binary"))
  jpg2 = Regexp.new("\xff\xd8\xff\xe1(.*){2}Exif".force_encoding("binary"))
  case IO.read(local_file_path, 10)
  when /^GIF8/
    'gif'
  when /^#{png}/
    'png'
  when /^#{jpg}/
    'jpg'
  when /^#{jpg2}/
    'jpg'
  else
    mime_type = `file #{local_file_path} --mime-type`.gsub("\n", '') # Works on linux and mac
    raise UnprocessableEntity, "unknown file type" if !mime_type
    mime_type.split(':')[1].split('/')[1].gsub('x-', '').gsub(/jpeg/, 'jpg').gsub(/text/, 'txt').gsub(/x-/, '')
  end  
end

1
你还需要查找"\xff\xd8\xff\xdb"作为JPEG签名。 - Richard Fairhurst

10

这是作为对这个答案的评论添加的,但实际上应该是一个独立的答案:

path = # path to your file

IO.popen(
  ["file", "--brief", "--mime-type", path],
  in: :close, err: :close
) { |io| io.read.chomp }

我可以确认它对我起作用了。


2
这个功能非常完美地工作,并且额外的好处是不需要添加和维护另一个 gem。 - Steven Hirlston
这个方法可以工作,但据我所知它信任文件扩展名。在大多数情况下这可能是好的,但使用文件的魔数更安全。在大多数情况下这显然不是问题。我提到这个原因只是因为我刚刚修复了一个bug,其中一个文件有“.jpeg”扩展名,但实际上是Gif格式。这很难调试,因为大多数方法都使用扩展名。 - Mig

7

如果您正在使用File类,可以根据@PatrickRichie的答案使用以下函数进行增强:

class File
    def mime_type
        `file --brief --mime-type #{self.path}`.strip
    end

    def charset
        `file --brief --mime #{self.path}`.split(';').second.split('=').second.strip
    end
end

如果你正在使用 Ruby on Rails,你可以将这个文件放到 config/initializers/file.rb 中,并在整个项目中使用。


5

对于通过搜索引擎来到这里的人,用纯Ruby查找MimeType的现代方法是使用mimemagic宝石。

require 'mimemagic'

MimeMagic.by_magic(File.open('tux.jpg')).type # => "image/jpeg" 

如果您认为只使用文件扩展名是安全的,则可以使用mime-types gem:
MIME::Types.type_for('tux.jpg') => [#<MIME::Type: image/jpeg>]

2
你可以试试使用shared-mime(gem install shared-mime-info)来进行检查。它需要使用Freedesktop共享mime信息库,并且可以进行文件名/扩展名检查以及“魔法”检查...我刚刚尝试了一下,但是我没有安装Freedesktop共享mime信息数据库并且需要做“真正的工作”,不幸的是,但这可能是你正在寻找的东西。

1

我最近发现了mimetype-fu

它似乎是获取文件MIME类型最简单可靠的解决方案。

唯一的注意点是,在Windows机器上它只使用文件扩展名,而在*Nix系统上它运行良好。


1

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