内容 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

官方 Ruby 常见问题解答

如果您希望报告此 FAQ 中的错误或提出改进建议,请访问我们的 GitHub 仓库 并提交 issue 或 pull request。

内置库

instance_methods(false) 返回什么?

方法 instance_methods 返回一个数组,其中包含接收类或模块中实例方法的名称。 这将包括超类和混合模块中的方法。

instance_methods(false)instance_methods(nil) 仅返回接收器中定义的方法的名称。

随机数种子如何工作?

如果调用 rand 时没有事先调用 srand,Ruby 的伪随机数生成器将使用一个随机(ish)种子,该种子除其他外还会使用操作系统提供的熵源(如果可用)。不使用 srand 的程序连续运行将生成不同的随机数序列。

出于测试目的,您可以通过每次在程序运行时使用常量种子调用 srand 来获得可预测的行为,即每次都产生相同的数字序列。

我读取了一个文件并对其进行了更改,但磁盘上的文件没有更改。

File.open("example", "r+").readlines.each_with_index do |line, i|
  line[0,0] = "#{i+1}: "
end

此程序不会将行号添加到文件 example。它确实读取了文件的内容,并且对于读取的每一行,都会在行号前添加行号,但数据永远不会写回。下面的代码确实会更新文件(尽管有点危险,因为它在开始更新之前没有进行备份)

File.open("example", "r+") do |f|
  lines = f.readlines
  lines.each_with_index {|line, i| line[0,0] = "#{i+1}: " }
  f.rewind
  f.puts lines
end

如何处理文件并更新其内容?

使用命令行选项 -i 或内置变量 $-i,您可以读取文件并替换它。

前面问题中向文件添加行号的代码最好使用此技术编写

$ ruby -i -ne 'print "#$.: #$_"' example

如果要保留原始文件,请使用 -i.bak 创建备份。

我写了一个文件,复制了它,但副本的末尾似乎丢失了。

此代码将无法正常工作

require "fileutils"

File.open("file", "w").puts "This is a file."
FileUtils.cp("file", "newfile")

由于 I/O 是缓冲的,因此在将其内容写入磁盘之前正在复制 filenewfile 可能为空。但是,当程序终止时,缓冲区将被刷新,并且文件具有预期的内容。

如果您确保在复制之前关闭 file,则不会出现此问题

require "fileutils"

File.open("file", "w") {|f| f.puts "This is a file." }
FileUtils.cp("file", "newfile")

如何获取当前输入文件中的行号?

当您从文件读取时,Ruby 会在全局变量 $. 中增加行号计数器。 这也可以使用 File 对象的 lineno 属性获得。

特殊常量 ARGF 是一个类似文件的对象,可用于读取在命令行上指定的所有输入文件(如果没有文件,则为标准输入)。 ARGF 由以下代码隐式使用

while gets
  print $_
end

在这种情况下,$. 将是跨所有输入文件读取的累计行数。 要获取当前文件中的行号,请使用

ARGF.file.lineno

您还可以使用 ARGF.file.path 获取当前文件的名称。

如何使用 less 显示程序的输出?

我尝试了以下操作,但没有任何输出

open("|less", "w").puts "abc"

这是因为程序会立即结束,而 less 永远没有机会看到您写入它的内容,更不用说显示它了。确保 IO 正确关闭,它将等待直到 less 结束。

open("|less", "w") {|f| f.puts "abc" }

如果不再引用 File 对象会发生什么?

不再引用的 File 对象将有资格进行垃圾回收。当垃圾回收 File 对象时,文件将自动关闭。

如果不关闭文件,我会感到不安。

至少有四种确保关闭文件的好方法

# (1)
f = File.open("file")
begin
  f.each {|line| print line }
ensure
  f.close
end

# (2)
File.open("file") do |f|
  f.each {|line| print line }
end

# (3)
File.foreach("file") {|line| print line }

# (4)
File.readlines("file").each {|line| print line }

如何按修改时间对文件进行排序?

Dir.glob("*").sort {|a, b| File.mtime(b) <=> File.mtime(a) }

虽然这可行(返回按时间倒序排列的列表),但效率不高,因为它在每次比较时都会从操作系统获取文件的修改时间。

可以通过一些额外的复杂性来提高效率

Dir.glob("*").map {|f| [File.mtime(f), f] }.
  sort {|a, b| b[0] <=> a[0] }.map(&:last)

如何计算文件中单词的频率?

freq = Hash.new(0)
File.read("example").scan(/\w+/) {|word| freq[word] += 1 }
freq.keys.sort.each {|word| puts "#{word}: #{freq[word]}" }

产生

and: 1
is: 3
line: 3
one: 1
this: 3
three: 1
two: 1

如何按字母顺序对字符串进行排序?

如果您希望字符串按 “AAA”, “BBB”, …, “ZZZ”, “aaa”, “bbb” 排序,则内置比较将正常工作。

如果要排序时忽略大小写区别,请在排序块中比较字符串的小写版本

array = %w( z bB Bb bb Aa BB aA AA aa a A )
array.sort {|a, b| a.downcase <=> b.downcase }
  # => ["a", "A", "Aa", "aA", "AA", "aa", "bB", "Bb", "bb", "BB", "z"]

如果您希望排序时使 “A” 和 “a” 放在一起,但 “a” 被认为大于 “A”(因此 “Aa” 在 “AA” 之后但在 “AB” 之前),请使用

array.sort {|a, b| (a.downcase <=> b.downcase).nonzero? || a <=> b }
  # => ["A", "a", "AA", "Aa", "aA", "aa", "BB", "Bb", "bB", "bb", "z"]

如何将制表符展开为空格?

如果 a 保存要展开的字符串,则可以使用以下方法之一

1 while a.sub!(/(^[^\t]*)\t(\t*)/){$1+" "*(8-$1.size%8+8*$2.size)}
# or
1 while a.sub!(/\t(\t*)/){" "*(8-$~.begin(0)%8+8*$1.size)}
# or
a.gsub!(/([^\t]{8})|([^\t]*)\t/n){[$+].pack("A8")}

如何在正则表达式中转义反斜杠?

Regexp.quote('\\') 转义反斜杠。

如果您使用 subgsub,则会变得更加棘手。假设您编写 gsub(/\\/, '\\\\'),希望将每个反斜杠替换为两个。在语法分析中,第二个参数转换为 '\\'。当发生替换时,正则表达式引擎将其转换为 '\',因此最终效果是将每个单反斜杠替换为另一个单反斜杠。您需要编写 gsub(/\\/, '\\\\\\')

但是,利用 \&amp; 包含匹配字符串的事实,您也可以编写 gsub(/\\/, '\&amp;\&amp;')

如果您使用 gsub 的块形式,即 gsub(/\\/) { '\\\\' },则替换字符串仅分析一次(在语法传递期间),结果是您想要的。

subsub! 之间有什么区别?

sub 中,会生成接收器的副本,进行替换并返回。

sub! 中,如果找到任何匹配项,则会更改并返回接收器。 否则,返回 nil

sub! 这样更改接收器属性的方法称为破坏性方法。通常,如果有两个类似的方法,并且一个是破坏性的,则破坏性方法会带有后缀 !

def foo(str)
  str.sub(/foo/, "baz")
end

obj = "foo"
foo(obj)  # => "baz"
obj       # => "foo"

def foo(str)
  str.sub!(/foo/, "baz")
end

foo(obj)  # => "baz"
obj       # => "baz"

\Z 在哪里匹配?

如果字符串以 \n 结尾,则 \Z 匹配最后一个 \n(换行符)之前的位置,否则它匹配字符串的末尾。

threadfork 之间有什么区别?

本节或其部分内容可能已过时或需要确认。

Ruby 线程在解释器内部实现,而 fork 调用操作系统来创建单独执行的子进程。

线程和 fork 具有以下特征

  • fork 很慢,thread 则不是。
  • fork 不共享内存空间。
  • thread 不会导致抖动。
  • thread 可以在 DOS 上工作。
  • thread 进入死锁时,整个进程都会停止。
  • fork 可以利用等待 I/O 完成的暂停,thread 则不能(至少没有一些帮助)。

您可能不应该混合使用 forkthread

如何使用 Marshal

Marshal 用于将对象存储在文件或字符串中,并在以后重新构成它。可以使用以下方法存储对象

Marshal.dump( obj [, io ] [, lev] )

io 是一个可写的 IO 对象,lev 指定对象被取消引用和存储的级别。如果完成了 lev 级别的取消引用并且对象引用仍然存在,则 dump 仅存储引用,而不存储引用的对象。这是不好的,因为这些引用的对象无法随后重建。

如果省略 io,则封送的对象将在字符串中返回。

您可以使用以下方法加载对象

obj = Marshal.load(io)
# or
obj = Marshal.load(str)

其中 io 是一个可读的 IO 对象,str 是转储的字符串。

如何使用 trap

trap 将代码块与外部事件(信号)相关联。

trap("PIPE") { raise "SIGPIPE" }