解析 Rbox 执行命令返回的文件统计信息

Parsing file stats returned by Rbox execute command

我是 Ruby 编程的新手,现在我正在使用 Rbox/Rye 编写一些测试自动化 ruby 脚本。
作为测试的一部分,客户将一个 +10GB 的文件上传到 ftp 主机。
该脚本每分钟检查文件是否仍在上传、上传完成或上传失败。
对于测试,我使用 Rbox/Rye 执行 shell 命令来查找文件统计信息:

def check_file_upload()
    rbox = Rye::Box.new("#{@host}")
    rbox.disable_safe_mode
    result = rbox.execute "stat #{@file}"
end

stats = file.check_file_upload()
puts stats

"puts" 正确打印文件统计信息。格式与我在 linux 主机上执行 "stat file" 命令相同。 现在,有没有一种方法可以让我真正解析
返回的值 check_file_upload()
方法。我可以使用 Ruby 的内置正则表达式,或者 grep/awk,从返回的文件统计信息中选择特定信息吗?
以下是文件统计:

File: `/home/user/file_name.dmp'  
Size: 11594768384   Blocks: 22668184   IO Block: 4096   regular file  
Device: 802h/2050d  Inode: 57442314    Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1504/user)   Gid: ( 1504/user)  
Access: 2015-01-06 11:32:17.000000000 -0700  
Modify: 2015-01-06 11:38:59.000000000 -0700  
Change: 2015-01-06 11:38:59.000000000 -0700 

更具体地说,我想选择文件大小、访问、修改和更改值

是的,这对于正则表达式来说是一项简单的任务。这是一个 class,它从 "stat" 字符串中解析有趣的位并将它们公开为属性:

class FileStatInfo
  attr_reader :size, :access, :modify, :change
  def initialize(string)
    string.split(/\n/).each do |line|
      if line =~ /^Size:\s*(\d+)/
        @size = .to_i
      elsif line =~ /^Access:\s*(.*)/
        @access = .strip # Or Date.parse(.strip)
      # ...
      end
    end
  end
end

# ...

stat_info = FileStatInfo.new(file.check_file_upload)
stat_info.size # => 11594768384
stat_info.access # => "2015-01-06 11:32:17.000000000 -0700"

一旦使用 check_file_upload() 提取了字符串 str,就可以构建散列来保存感兴趣的值。

代码

def extract(str, keepers)
  r = /\w+:\s(?:\S+\s)*\S+/
  str.lines.each_with_object({}) do |line, h|
    line.scan(r).each do |s|
      k,_,v = s.partition(/:\s+/)
      v = v.to_i if v =~ /^\d+$/
      h.update({ k=>[v] }) { |_,ov,nv| ov+nv } if keepers.include?(k)
    end
  end
end

例子

我理解以下是 check_file_upload() 生成的典型字符串:

str =
"Size: 11594768384   Blocks: 22668184   IO Block: 4096   regular file  
Device: 802h/2050d  Inode: 57442314    Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1504/user)   Gid: ( 1504/user)  
Access: 2015-01-06 11:32:17.000000000 -0700  
Modify: 2015-01-06 11:38:59.000000000 -0700  
Change: 2015-01-06 11:38:59.000000000 -0700"

数组keepers标识要从此字符串中提取的数据:

keepers = %w{ Size Access Modify Change }
  #=> ["Size", "Access", "Modify", "Change"]

extract(str, keepers)
  #=> {"Size"=>[11594768384],
  #    "Access"=>["(0644/-rw-r--r--)", "2015-01-06 11:32:17.000000000 -0700"],
  #    "Modify"=>["2015-01-06 11:38:59.000000000 -0700"],
  #    "Change"=>["2015-01-06 11:38:59.000000000 -0700"]} 

说明

我们的正则表达式是:

r = /\w+:\s(?:\S+\s)*\S+/

对于上面的例子:

str.lines
  #=> ["Size: 11594768384   Blocks: 22668184   IO Block: 4096  regular file \n",
  #    "Device: 802h/2050d  Inode: 57442314    Links: 1\n",
  #    "Access: (0644/-rw-r--r--)  Uid: ( 1504/user)   Gid: ( 1504/user)  \n",
  #    "Access: 2015-01-06 11:32:17.000000000 -0700  \n",
  #    "Modify: 2015-01-06 11:38:59.000000000 -0700  \n",
  #    "Change: 2015-01-06 11:38:59.000000000 -0700"]

Enumerable#each_with_object 创建一个由块变量 h 表示的初始为空的散列,并将数组 str.lines 的每个元素传递到块中,并将其分配给块变量 line。最初,

line = "Size: 11594768384   Blocks: 22668184   IO Block: 4096  regular file \n"
h = {}

在我们计算的块内:

a = line.scan(r)
  #=> ["Size: 11594768384", "Blocks: 22668184", "Block: 4096"] 

请注意 "IO Block: 4096 regular file" 提取不正确,但这无关紧要,因为不需要该字段。

a.each do |s|
  k,_,v = s.partition(/:\s+/)
  v = v.to_i if v =~ /^\d+$/
  h.update({ k=>[v] }) { |_,ov,nv| ov+nv } if keepers.include?(k)
end

h #=> {"Size"=>[11594768384]} 

在这里,第一个字符串 each 传入它的块是

s = "Size: 11594768384"

所以

k,_,v = s.partition(/:\s+/)
  #=> ["Size", ": ", "11594768384"]
v =~ /^\d+$/ #=> 0 
v = v.to_i if v =~ /^\d+$/
  #=> 11594768384
h.update({ k=>[v] }) { |_,ov,nv| ov+nv } if keepers.include?(k)
  #=> {}.update({ "Size"=>[11594768384] }) if
  #     ["Size", "Access", "Modify", "Change"].include?("Size")
  #=> true
h #=> { "Size=>[11594768384] }

我们使用 Hash#update (a.k.a. merge!) 的形式,它使用一个块来解析包含在两个被合并的哈希值中的键的值。

由于 keepers 既不包含 "Blocks" 也不包含 "Block",这些键的哈希值不会合并到 h.

对于 str.lines 的下一个元素:

line = "Device: 802h/2050d  Inode: 57442314    Links: 1\n"

keepers 中不包含任何键,因此不会向散列 h 添加任何内容。下面一行:

line = "Access: (0644/-rw-r--r--)  Uid: ( 1504/user)   Gid: ( 1504/user)  \n"

{ "Access"=>["(0644/-rw-r--r--)"] } 添加到散列中,因此我们有:

h = { "Size"=>[11594768384], "Access"=>["(0644/-rw-r--r--)"] }

接下来,

line = "Access: 2015-01-06 11:32:17.000000000 -0700  \n"

所以

a = line.scan(r)
  #=> ["Access: 2015-01-06 11:32:17.000000000 -0700"] 

从该数组传入其块的唯一元素 each 是:

s = "Access: 2015-01-06 11:32:17.000000000 -0700"

k,_,v = s.partition(/:\s+/)
  #=> ["Access", ": ", "2015-01-06 11:32:17.000000000 -0700"] 
v =~ /^\d+$/
  #=> nil 
v = v.to_i if v =~ /^\d+$/ # no conversion to integer
h.update({ k=>[v] }) { |_,ov,nv| ov+nv } if keepers.include?(k)
  #=> {"Size"=>[11594768384],
  #    "Access"=>["(0644/-rw-r--r--)", "2015-01-06 11:32:17.000000000 -0700"]} 

由于h已经包含键k => Access"update的块在执行合并后被调用以确定"Access"的值。三个块变量是:

k = "Access" # not used, so replaced with an underscore
ov = ["(0644/-rw-r--r--)"                    # "old" value
nv = ["2015-01-06 11:32:17.000000000 -0700"] # "new" value

"Access"的值就是数组ovnv之和,如上图

str.lines 的其余元素以类似方式处理。