Ruby 基础笔记
Table of Contents
1 说明
为自己整理的一份 Ruby 基础知识笔记,便于平时使用,目前看起来还是杂乱无章的。
参考资料:
- 《Ruby 基础教程》,作者高桥征义,后藤裕藏。
- 《Ruby 元编程》,作者 Paolo Perrotta。
- Ruby 官方文档、StackOverflow、Ruby China 等各大技术社区的讨论内容。
2 准备工具
- pry,交互式环境,比自带的irb好用;
- Ruby 编程风格指南:https://github.com/JuanitoFatas/ruby-style-guide/blob/master/README-zhCN.md;
- rubocop,代码风格检查工具;
- RVM,类似 Python 的 Virtualenv,多版本 Ruby 和 Gem 管理;
- gem,Ruby 的包管理器。
3 基础
3.1 注释
- 单行注释符:#
- 多行注释:
=begin 注释内容 =end
3.2 打印输出
puts "hello world" print "hello world"
print 和 puts 区别:
print/printf 默认不会打印换行符,puts 默认会打印换行符。
p 方法:
用于区分输出对象,字符串对象会自动加上双引号来区分:
> p "hello world" "hello world" => "hello world"
3.3 常量变量
3.3.1 变量
变量的分类:
- 局部变量,以英文字母或 _ 开头;
- 全局变量,以 $ 开头;
- 实例变量,以 @ 开头;
- 类变量,以 @@ 开头;
- 伪变量,nil、true、false、self。
3.3.1.1 赋值
a = 1 a, b, c = 1, 2, 3 a, b, c = 1, 2 # c 会被忽略,赋值为 nil # 加“*”表示把未分配的值赋值给变量: a, b, *c = 1, 2, 3, 4, 5, 6 # => a=1, b=2, c=[3, 4, 5, 6] a, *b, c = 1, 2, 3, 4, 5 # => a=1, b=[2, 3, 4], c=5 # 数组解构 a, b = [1, 2] a, = [100, 200] # a=10 a, b, c = [1, [2, 3], 4] # a=1, b=[2, 3], c=4
3.3.1.2 引用
在字符串中引用变量的值:
> msg = "hello, world" => "hello, world" > "#{msg}" => "hello, world"
如果要把“#{msg}”表示成字符串,需要加单引号:
'#{msg}' => "\#{msg}"
3.3.1.3 特殊变量
__FILE__:本身的文件名。
$0:当前运行的文件名:
if __FILE__ == $0 main end
等价于 Python 的:
if __name__ == '__main__': main()
3.3.2 常量
Ruby 规定全部大写字母的为常量,且定义后不能被修改:
> A = 1 => 1 > A = 2 (pry):2: warning: already initialized constant A (pry):1: warning: previous definition of A was here => 2
常量的引用路径:
Y = 'root' # 根据“路径”来引用常量 module M Y = 'self' puts Y puts ::Y end puts M::Y
3.4 字符串
双引号或单引号之间的内容是字符串:
"hello world" 'hello world'
两者区别在于单引号的内容不会被转义:
> print 'hi\n' => hi\n
3.4.1 常用操作
to_i,转成数字:
> "1".to_i => 1
判断空字符串:
> ''.empty? => true > 'a'.empty? => false
%Q,添加转移字符:
> %Q{your's book} => "your's book"
Here Document(来源 UNIX 的 shell 写法):
> <<EOF | hahah | oo | fawef | EOF => "hahah\noo\n fawef\n"
编码转换:
encode 和 encode! 方法,后者提供破坏性操作。
格式化:
> "%d" % 1 => "1" # 控制输出的固定位数 > "%02d" % 1 => "01"
3.5 数组
3.5.1 创建数组
> a = [1, 2, 3] => [1, 2, 3] > a = [1, 2, 3,] # 多一个逗号也可以
3.5.2 索引
> a[0] => 1 > a[10] # 越界访问返回 nil,不像 Python 报出 IndexError 异常 => nil
数组还可以按范围索引:
> a = (1..10).to_a # 创建 1~10 的连续数组 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] > a[1..3] # 取下标 1~3 的内容 => [2, 3, 4] > a[0..3] # 取下标 0~3 的内容 => [1, 2, 3, 4]
3.5.3 修改元素
> a = [1, 2, 3] => [1, 2, 3] > a[0] = 100 # 修改下标为 0 的内容 => 100 > a => [100, 2, 3] > a << 4 # 追加元素,注意这是有副作用的,会修改原始的数组 => [100, 2, 3, 4]
3.5.4 遍历数组
调用 each 方法,each 方法接收一个 block 对象:
> a.each do |i| * print i * end 123111=> [1, 2, 3, nil, nil, nil, nil, nil, nil, nil, 111]
3.5.5 动态数组
如果越界赋值,Ruby 会自动填充中间部分:
> a[10] = 111 => 111 > a => [1, 2, 3, nil, nil, nil, nil, nil, nil, nil, 111]
3.5.6 数组大小
> a.size => 11
3.5.7 动态调整数组大小
> a = [1, 2, 3] => [1, 2, 3] > a[1, 0] = ['2', '3'] # 把['2', '3']插入到下标 1 开始的第 0 个元素后面 => ["2", "3"] > a => [1, "2", "3", 2, 3] > a = [1, 2, 3, 4, 5] => [1, 2, 3, 4, 5] > a[2..4] = [1] => [1] irb(main):331:0> a # 从下标 2~4 的元素已经被替换成另外一个数组[1] => [1, 2, 1]
3.5.8 集合操作
3.5.8.1 交集
> [1, 2, 3] & [1, 3, 5] => [1, 3]
3.5.8.2 并集
> [1, 2, 3] | [1, 3, 5] => [1, 2, 3, 5]
3.5.8.3 集差
> [1, 2, 3] - [1, 3, 5] => [2]
3.6 range
范围对象,可以通过下面三种方式创建:
# 方式1,显示创建方法 Range.new(1, 10) # 元素为 1~10 # 方式2 1..10 # 元素为 1~10 # 方式3 1...10 # 元素为 1~9
除了生成数字范围,还可以生成字母范围:
> ('a'..'h').to_a => ["a", "b", "c", "d", "e", "f", "g", "h"]
3.6.1 转成list对象
Range 对象调用 to_a 表示转换成 list
> (1..10).to_a => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
3.6.2 遍历
(14..31).each do |i| puts i end
3.7 散列表
3.7.1 创建散列表
> {name: "lu4nx", site: "www.shellcodes.org"} => {:name=>"lu4nx", :site=>"www.shellcodes.org"} > {"name": "lu4nx", site: "www.shellcodes.org"} # 表达方式都一样 => {:name=>"lu4nx", :site=>"www.shellcodes.org"} > {"name" => "lu4nx", "site" => "www.shellcodes.org"} # 如果希望字符串做散列表,就这样创建 => {"name"=>"lu4nx", "site"=>"www.shellcodes.org"}
3.7.2 散列表索引
> info = {name: "lu4nx", site: "www.shellcodes.org"} => {:name=>"lu4nx", :site=>"www.shellcodes.org"} > info[:name] => "lu4nx"
3.7.3 散列表遍历
> info.each do |k, v| * puts k, v * end name lu4nx site www.shellcodes.org => {:name=>"lu4nx", :site=>"www.shellcodes.org"}
3.8 符号
符号也是对象。符号对象在判断时只用对比是否是一个对象,比字符对比更加有效率。
将转换成字符串对象:
> :a.to_s => "a"
字符串也可以转换成符号:
> "a".to_sym => :a
3.9 块
代码块(block)有两种写法:
do...end
和
{...}
只是风格上的区别,一般需要跨行,就用 do…end,单行的用 {…},如:
# 多行代码块 10.times do |i| puts i end # 单行代码块 10.times {|i| puts i}
3.9.1 定义一个块
def xx(n) puts 'hi' yield(n) end xx(10) do |n| puts n end
个人感觉 Ruby 的块,看上去很像 Lisp 的宏。例如,定义一个循环:
def loop1 while true yield print "hi" end end loop1 do print 1 break end
3.9.2 判断是否有块
def xx(n) puts 'hi' if block_given? yield(n) end end xx(10) do |n| puts n end xx(10) # 不带块操作
在块中遇到 break 会马上中断块执行:
def xx(n) puts 'hi' if block_given? yield(n) end end xx(10) do |n| break puts n # 这里不会执行 end
3.9.3 用块隐藏常规处理
如下,实现了类似 Python 的 with open:
File.open('/etc/passwd') do |f| f.each_line do |line| puts line end end
这里,不用手动调用 close 关闭文件操作符,因为块里已经实现了。
3.9.4 块对象
块还可以封装成对象,用 Proc 类:
hello = Proc.new do |name| puts name end hello.call('lx')
封装成对象后,就可以传递了:
def exec_block(&b) b.call end exec_block() do puts 'hi' end
& 表示接受一个块对象,如果传递了块,值就为块对象,否则为 nil。
3.9.5 lambda
等价 Proc
hi = lambda do puts 'hi' end hi.call
3.10 控制结构
3.10.1 逻辑运算
布尔关键字:true 和 false。
与:&& 或:|| 非:!
3.10.2 条件判断
3.10.2.1 if 判断
if ... then ... end # 或者: if ... then ... elsif ... then ... else ... end
注1:上面的 then 可以省略。
注2:nil 和 false 以外的均为 true。
3.10.2.2 条件运算
..?..:..
例:
> a = 1 => 1 > b = 2 => 2 > c = (a > b) ? true : false => false
3.10.2.3 unless
与 if 的相反。
unless ... then ... end
3.10.2.4 case
类似 C 语言的 switch。
case 对象 when 值1 then ... when 值2 then ... else ... end
例1,对象类型判断:
a = ["a", 1, nil] a.each do |i| case i when String puts String when Numeric puts Numeric else puts "I don't known" end end
例2,case 中使用正则匹配:
f = File.open("/etc/passwd") f.each_line do |line| case line when /lu4nx/ puts line when /root/ puts line else end end
3.10.2.5 =
更严格的判断,可用来判断正则是否相等、左边对象是否属于右边的类,如:
> String === 'a' => true > /a/ === 'abc' => true
实际上 case 语句就是用的”===“做判断。
3.10.2.6 判断是否是同一对象
每个对象都有一个 object_id 或者__id__属性:
> a.object_id => 3 > a.__id__ => 3
用 equal? 方法判断是否是同一对象:
> a.equal?(a) => true > a.equal?(b) => false
3.10.2.7 eql? 方法
类似 Common Lisp 中的 eql,用作更严谨的判断,如:
> 1 == 1.0 => true > 1.eql?(1.0) => false
3.10.3 循环
3.10.3.1 for
for 陷阱:for 不会开辟新的作用域,for 中定义的变量对外是可见的。
for i in 1..5 do puts i end
注,关键字“do”可以省略。
3.10.3.2 while
while ... do ... end
注:do 可以省略。
times 方法:
> 10.times do print 'a' end aaaaaaaaaa=> 10
3.10.3.3 until
i = 100 until i <= 0 do i -= 1 end print i
3.10.3.4 times 方法
按次数循环可用数字类型的 times 方法。
10.times do print 'a' end
3.10.3.5 each 方法
遍历序列对象可直接用 each 方法:
[1, 2, 3].each do |i| puts i end
3.10.3.6 loop——无限循环
loop do puts 1 end
无限循环也可以用 while true 代替:
while true do puts 1 end
3.10.4 循环控制
3.10.4.1 break
打断循环流程。
for i in (1..100).to_a if i == 49 break end end
3.10.4.2 next
跳到下一次循环,等价于其他语言的”continue“关键字。
for i in (1..100).to_a if i != 49 next else print i end end
3.10.4.3 redo
redo 会重新跳入循环,这是很少使用的关键字。
i = 0 ["Perl", "Python", "Ruby", "Scheme"].each do |lang| i += 1 if i == 3 redo end p [i, lang] end
输出如下:
[1, "Perl"] [2, "Python"] [4, "Ruby"] [5, "Scheme"]
执行到 i 等于 3 时,又重新跳入循环,导致 i 多加了一次 1。
3.11 异常处理
begin 可能会发生异常的代码 rescue => 引用异常对象的变量(不指明对象将异常赋值给 $!) 发生异常后的处理 end
如:
begin File.read('/etc/passwd1') rescue => e puts e end
还可以写成:
begin File.read('/etc/passwd1') rescue puts $! end
通过 [email protected] 获得最后异常信息:
begin File.read('/etc/passwd1') rescue puts [email protected] end
输出:
x.rb:2:in `read' x.rb:2:in `<main>'
3.11.1 异常对象
异常对象有几个方法:
begin File.read('/etc/passwd1') rescue puts [email protected].class # 获得异常信息种类 puts [email protected].message # 获得异常信息 puts [email protected].backtrace # 获得异常信息调用栈 end
3.11.2 ensure
不管是否异常,都会调用 ensure 块的内容:
begin File.read('/etc/passwd1') rescue puts [email protected] ensure puts 'bye.' end
3.11.3 异常重试
发生异常后,还可以用 retry 关键字让它重试:
begin File.read('/etc/passwd1') rescue puts [email protected] sleep(1) retry end
一定要注意如果一直重试失败,会陷入死循环。
3.11.4 rescue 修饰符
表达式1 rescue 表达式2
表达式1出现异常后,调用表达式2,如:
> Integer('a') rescue 0 => 0 > Integer(1) rescue 0 => 1 > Integer('a') ArgumentError: invalid value for Integer(): "a" from (pry):3:in `Integer'
3.11.5 省略 begin 和 end 关键字
如果整块代码都在异常处理内,可以省略 begin 和 end:
def xx(s) return Integer(s) rescue return 0 end puts xx('a') puts xx(1)
3.11.6 捕获多个异常
rescue 异常1 => 变量 rescue 异常2 => 变量
例:
def xx(s) return Integer(s) rescue ArgumentError return 0 rescue TypeError return -1 end puts xx('a') puts xx(1) puts xx(nil) # => -1
3.11.7 定义新的异常
所有异常都是 Exception 的子类:
def xx(s) return Integer(s) rescue Exception return 0 end puts xx('a') puts xx(1) puts xx(nil) # => 0
如果 rescue 不指定异常,默认是捕获 StandardError 异常。
定义新异常:
XXError = Class.new(StandardError) # 表示继承 StandardError
3.11.8 抛出异常
raise 异常类 raise 异常类, 消息 raise # 抛出 RuntimeError
例如,抛出上面刚定义的新异常 XXError:
XXError = Class.new(StandardError) def xx(s) raise XXError rescue XXError return 0 end puts xx('a') # => 0 puts xx(1) # => 0 puts xx(nil) # => 0
Ruby 没有 assert 关键字,可以用 raise … unless 组合起来实现类似的功能:
raise "domain is nil" unless domain != nil
3.12 正则表达式
3.12.1 正则匹配
/正则/ =~ "要匹配的字符串"
例:
> /world/ =~ "hello world" => 6
如果匹配成功,返回匹配的字符第一次出现的位置(从0开始算),否则返回 nil。
正则后加 i 表示不区分大小写:
> /WORLD/i =~ "hello world" => 6
3.12.2 提取数据
> url = "http://www.shellcodes.org/" > host = url.scan(/http:\/\/([\w\.]+)\//) => [["www.shellcodes.org"]]
4 模块
模块提供一个命名空间机制。
4.1 引用模块
4.1.1 require 关键字
require "[库名]"
如果引用的文件在当前目录,库名需要加“./”(不需要加 .rb):
require './hello'
在模块化的时候,注意变量的作用域。如果被引用文件里定义的变量不是全局变量,在引用模块里是不能引用这个变量的。
实例,引用 pp 库(可美化打印内置对象的内置库):
require "pp" v = [{ name: "lu4nx", site: "www.shellcodes.org" }, { name: "lu4nx", email: "lx_at_shellcodes.org" }] pp v
输出:
[{:name=>"lu4nx", :site=>"www.shellcodes.org"}, {:name=>"lu4nx", :email=>"lx_at_shellcodes.org"}]
4.1.2 include 关键字
导出模块里所有名字,就可以不通过模块名来访问,一般最好不要这么做,否则有重名的风险:
[6] pry(main)> include Math => Object [7] pry(main)> PI => 3.141592653589793 # 可以直接访问
4.2 创建模块
# 创建一个模块 module LX # 模块常量 Version = "1.0" def say(msg) puts msg end # 获得模块自身信息,保存在 self 变量中 def self_object self end # 不这么定义的函数或者类,只能在上下文中引用, # 不能被外部引用以“模块名.方法名”引用 module_function :say end
4.3 扩展模块
用 extend 方法可以扩展模块:
module LX Version = "1.0" def say(msg) puts msg end end s1 = "hi" s1.extend(LX) s1.say(s1) # => hi
5 类
Ruby 是完全面向对象的,所有的“类型内置”实质都是类对象,列举如下:
- 数值:Numeric
- 字符串:String
- 数组:Array
- 散列:Hash
- 正则表达式:Regexp
- 文件:File
- 符号:Symbol
可以通过 class 方法来查看对象所属类:
> "hello".class => String > 1.class => Fixnum > [1, 2, 3].class => Array
类本身也是对象,所以类也有自身的类:
> Array.class => Class > String.class => Class
用 instance_of? 方法可以判断对象是否属于某个类:
> a.instance_of?(Array) => true > a.instance_of?(String) => false
5.1 初始化类
调用 new 方法:
a = Array.new
5.2 类继承
BasicObject 是 Ruby 中所有类的父类。
子类与父类的关系称为 is-a ,根据继承关系反向查对象时,可用 is_a?:
> a.is_a?(Object) => true
继承一个类,如下:
class NewString < String def hi puts "hi" end end s = NewString.new("hi") puts s s.hi
BasicObject 提供了一个类最低限度的方法,所以可以通过它继承。
可用 ancestors 方法获得集成关系:
> Integer.ancestors => [Integer, Numeric, Comparable, Object, Math, PP::ObjectMixin, Kernel, BasicObject]
用 superclass 方法获得父类:
> Integer.superclass => Numeric
5.3 定义类
示例如下:
# coding: utf-8 class Hello def initialize(msg) # 构造函数 @msg = msg # 成员变量 end def show_msg puts @msg # 引用成员变量 end def msg # 外部不能直接访问成员变量,需要定义“存取器” @msg end def msg=(new_msg) @msg = new_msg end end hello = Hello.new("hi") hello.show_msg puts hello.msg hello.msg = 'new msg' hello.show_msg
5.3.1 存取器
上面的例子定义了 msg 的 reader 和 writer 方法,如果有太多成员变量的话,会很不方便。所以 Ruby 允许定义存取器:
attr_reader :name,只读。 attr_writer :name,只写。 attr_accessor :name,读写,合称accessor。
示例代码:
class Hello attr_reader :msg attr_writer :msg # 实现读写直接用attr_accessor :msg def initialize(msg) # 构造函数 @msg = msg # 成员变量 end def show_msg puts @msg # 引用成员变量,可以不加 @ 开头 end end hello = Hello.new("hi") hello.show_msg puts hello.msg hello.msg = 'new msg' hello.show_msg
5.3.2 self 变量
self 用来引用接收者,上面代码稍改下:
def show_msg puts self.msg end
self 可以用来解决方法中有重名的局部变量:
def show_msg msg = 'hi' # 这是局部变量 puts self.msg end
5.4 定义类方法
类方法就是接收对象是类,而不是实例:
class << Hello def say(msg) puts msg end end Hello.say("new hello")
方法调用 return 返回值,如果省略的话,返回值就是方法最后一条语句执行结果。
Ruby 有三种方法:
- 对象方法;
- 类方法;
-函数式方法。
对象方法
消息接收者是一个对象,如几乎每个对象都有 to_s 方法:
> a = [1, 2, 3] => [1, 2, 3] > a.to_s => "[1, 2, 3]"
类方法 类似静态方法
> File.read('/etc/issue') => "Fedora release 22 (Twenty Two)\nKernel \\r on an \\m (\\l)\n\n" > File::read('/etc/issue') # 也可以这么调用 => "Fedora release 22 (Twenty Two)\nKernel \\r on an \\m (\\l)\n\n"
函数式方法 没有对象接收者的方法,外观看上去并不像方法调用,更像是调用函数:
print "hello world"
类方法是可以和对象方法重名的,虽然可以重名,但也可以定义到类中,用 self:
class Hello attr_reader :msg attr_writer :msg # 实现读写直接用 attr_accessor :msg class << self def say(msg) puts msg end end def initialize(msg) # 构造函数 @msg = msg # 成员变量 end def show_msg msg = 'hi' puts self.msg # 引用成员变量 end end hello = Hello.new("hi") hello.show_msg Hello.say("new hello")
此外,还可以这样定义:
def Hello.say(msg) puts msg end
甚至:
def self.say(msg) # 因为还在 class 的上下文中,所以能这么定义 puts msg end
5.4.1 方法参数
默认参数:
def say(msg='no msg') puts msg end
不定参数:
def foo(*args) args end print foo(1, 2, 3)
关键字参数:
注:Ruby2.0 之后才支持 def foo(msg: nil) print msg end foo(msg: 'hi') foo()
分解参数:
通过散列方式传递参数:
foo({msg: 'hi'})
通过数组分解:
def foo(a, b, c) print a, b, c end foo(*[1, 2, 3])
5.4.2 类中定义常量
示例如下:
class Hello DEFAULT_MSG = 'default msg' # 常量定义 def say(msg) if msg == '' puts DEFAULT_MSG else puts msg end end end
5.5 类变量
所有实例都共享的变量就是 类变量 ,定义时以 @@ 开头:
class TheI @@count = 0 def count puts @@count end def plus @@count += 1 end end the_i1 = TheI.new the_i1.count the_i2 = TheI.new the_i2.count the_i1.plus the_i1.count # => 1 the_i2.count # 也是 1
5.6 访问权限
- public:默认;
- private:无法从外部访问;
- protected:子类可访问。
class Hi def self_known puts 'self' end def you_known puts 'you known' end def i_want_access_self_known self_known end # 设置访问级别 public :you_known # 默认就是 public private :self_known end hi = Hi.new hi.you_known hi.i_want_access_self_known
还可以用更统一的方式定义:
class Hi # 以下方法都为 private 级别 private def self_known puts 'self' end # 以下方法都为 public 级别 public def you_known puts 'you known' end def i_want_access_self_known self_known end end hi = Hi.new hi.you_known hi.i_want_access_self_known
5.7 扩展类
其他语言只允许你读取类的相关信息(这种技术称为”自省“),而 Ruby 允许你在运行时修改这些信息。Ruby 有个很强的特性就是可以对已存在的类进行打开并扩充,一个实际应用的例子,把对象转成 YAML 格式:
> require 'yaml' => true > [1, 2, 3].to_yaml => "---\n- 1\n- 2\n- 3\n"
扩充一个类,首先”进入“这个类,然后为它添加新方法。如下,为 String 类添加新方法:
class String def hi puts "hi" end end s = "hello" s.hi
5.8 类别名
class C1 def hello puts "hello" end end class C2 < C1 alias old_hello hello # 别名化父类的“hello”方法 # 重定义 hello def hello puts "hello world" end end c = C2.new puts c.hello puts c.old_hello
5.9 删除已定义的方法
类中用 undef 关键字可以删除指定的方法,示例如下:
class C1 def hello puts "hello" end def say(msg) puts msg end end class C2 < C1 alias old_hello hello # 重定义 hello def hello puts "hello world" end # 不要父类中的 say 方法 undef say end c = C2.new puts c.hello puts c.old_hello c.say('hi')
5.10 单例类
str1 = "Ruby" str2 = "Ruby" class << str1 def say(msg) puts msg end end str1.say("hehe") str2.say("hehe") # => undefined method `say' for "Ruby":String
5.11 Mix-in
Mix-in 从概念上说就是一个类包含了其他类的方法。Ruby 是单一继承,但实现了 Mix-in,所以也可以实现多重继承。
Mix-in 相当于给类添加了实例方法:
module LX Version = "1.0" def say(msg) puts msg end end class C include LX end c = C.new c.say("hehe")
单一继承让类的层次关系更清晰,多重继承会增加类层次的杂性。但是有些情况下还是需要多重继承,比如实现一个 Stream 类,Read 和 Write 都是它的子类,但是要实现 ReadAndWrite,就必须多重继承了。实现代码如下:
class Stream end module Reader def read puts 'read' end end module Writer def write puts 'write' end end class Read < Stream include Reader end class Write < Stream include Writer end class ReadAndWrite include Reader include Writer end rw = ReadAndWrite.new rw.read # => read rw.write # => write
5.12 refine
猴子补丁会在不知不觉中修改类,所以从 Ruby2.0 开始增加了 refine 功能。下面是示例代码:
# refine需要定义一个模块 module StringExt refine String do def say(msg) puts msg end end end using StringExt # 明确使用了 using 关键字,扩展类才会生效 'hi'.say('hello world')
using 的作用域在当前模块结束,如果是顶层(main)中调用 using,有效范围直到文件结束。从 Ruby2.1 开始,也可以让 using 的作用域仅模块内有效,上面代码修改如下:
# refine 需要定义一个模块 module StringExt refine String do def say(msg) puts msg end end end module Say using StringExt 'hi'.say('hello world') # hello world end 'hi'.say('hello world') # undefined method `say' for "hi":String (NoMethodError)
6 其他
6.1 I/O 操作
6.1.1 读写文件
读:
> File.new("/etc/hosts").read
写:
> f = File.new("/tmp/xx", "w") => #<File:/tmp/xx> > f.puts "test" => nil > f.close => nil
6.1.2 ARGV 数组
保存脚本运行时的参数。注意第 0 个元素保存的是参数 1,而不是脚本文件名。
一个读取文件示例:
filename = ARGV[0] f = File.open(filename) puts f.read f.close
更短小:
filename = ARGV[0] puts File.read(filename)
还要短小:
puts File.read(ARGV[0])
6.1.3 逐行遍历文件
f = File.open(ARGV[0]) f.each_line do |line| puts line end f.close
例,一个grep的简单实现:
filename = ARGV[0] pattern = Regexp.new(ARGV[1]) f = File.open(filename) f.each_line do |line| if pattern =~ line puts line end end f.close
6.1.4 标准输入
STDIN.each_line do |line| puts line end
6.2 调用系统命令
1、反引号:
> `pwd` => "'/home/lu4nx\n"
2、exec 函数:
> exec "pwd" /home/lu4nx
3、system 函数:
> system "pwd" /home/lu4nx => true