输入/输出的种类
标准输入/输出
程序在启动后会预先分配3个IO对象。
标准输入
标准输入是用于接收数据的IO对象。可以通过预定义常量STDIN调用IO对象,也可以通过全局变量$stdin引用IO对象。不指定接受者的gets方法等都会默认从$stdin中获取数据。标准输入最初与控制台关联,接收从键盘输入的内容。
标准输出
标准输出是用于输出数据的IO对象。可以通过预定义常量STDOUT调用IO对象,也可以用全局变量$stdout引用IO对象。不知道接受者的puts、print、printf等方法会默认将数据输出到$stdout。标准输出最初与控制台关联
标准错误输出
标准错误输出是用于输出警告、错误的IO对象。可以通过预定义常量STDERR调用IO对象,也可以用全局变量$stderr引用IO对象。用于显示警告信息的warn方法会将信息输出到$stderr。标准版错误输出最初与控制台关联
代码
3.times do |i| $stdout.puts "#{Random.rand}" STDERR.puts "已经输出了#{i+1}次" end
通过一个标准输出,一个错误输出,只有标准输出会被写入文件。
通过tty检查标准输入是否为屏幕的例子
shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ ruby tty.rb /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/universal-darwin19/rbconfig.rb:229: warning: Insecure world writable dir /usr/local/opt/mysql@5.7/bin in PATH, mode 040777 Stdin is a TTY. shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ echo | ruby tty.rb /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/universal-darwin19/rbconfig.rb:229: warning: Insecure world writable dir /usr/local/opt/mysql@5.7/bin in PATH, mode 040777 Stdin is not a TTY. shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ ruby tty.rb < log.txt /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/universal-darwin19/rbconfig.rb:229: warning: Insecure world writable dir /usr/local/opt/mysql@5.7/bin in PATH, mode 040777 Stdin is not a TTY. shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ cat tty.rb if $stdin.tty? print "Stdin is a TTY. " else print "Stdin is not a TTY. " end shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$
文件的输入与输出
通过IO类的子类File类可以进行文件的输入/输出操作。
io = File.open(file [, mode[,perm]][, opt])
io = open(file [, mode[,perm]][, opt])
通过上面的方法可以获得一个io对象
mode默认是"r",可以通过设定指定的值,模式跟Python中的open差不多
File操作中,可以通过块变量传递给块。块执行完毕后,块变量引用的File对象也会自动关闭。像Python中的with open as f
File.open("foo.txt") do |file| while line = file.gets ... #执行逻辑 end end
file.closed?检查file对象是否关闭
File.read(file,[, length [, offset]])
不创建File对象,直接读取file里面的数据。length指定读取长度,offset指定从前面第几个字节开始读取数据。如忽略这些参数,程序会从头到位一次性读取文件内容
File.binread(file,[, length [, offset]])
二进制的方式读取文件
FIle.write(file[,data[,offset]])
不创建File对象,直接向file写入data。省略参数offset时,会将文件的全部内容替换为data,指定该参数时,则会将前面offset个字节写入文件,后面的数据则保持不变
>> text = "Hello,Ruby! " => "Hello,Ruby! " >> File.write("hello.txt", text) => 12 >> p File.read("hello.txt") "Hello,Ruby! " => "Hello,Ruby! " >> File.write("hello.txt", "12345", 5) => 5 >> p File.read("hello.txt") "Hello12345! " => "Hello12345! " >>
File.binwrite(file[,data[,offset]])
以二进制模式打开并写入file
基本的输入/输出操作
对于io对象的基本操作。
输入操作 io.gets,io.each,io.each_line,io.readlines
io.gets(rs) rs为分隔符,默认为" "
这个方法就像逐行读取,默认读取的时候带 ,可以通过chmop!去除
可以通过eof?去判断有没是否输入完毕
io.each 与io.each_line返回的是一个枚举对象了
irb(main):001:0> io = open("log.txt") => #<File:log.txt> irb(main):002:0> io.each.class => Enumerator irb(main):003:0>
io.reandline方案可以差异性读取所有数据,并返回将每行数据作为元素封装的数组。
io.lineno
使用gets方法、each_line方法逐行读取数据时,会自动记录读取的行数。这个行数可以通过lineno方法获取。
io.each_char
io.eache_char do |ch|
...
end
逐个字符地读取io中的数据并执行块,将读取的字符(String对象)作为块变量的传递
io.each_byte
逐个字节的读取io中的数据并启动块,将读取到的字节所对应的ASCII码以整数值的形式传递给块变量
io.getc
while ch = io.getc
...
end
只读取io中的一个字符。
io.ungetc(ch)
将参数ch指定的字符退回到io到输入缓冲区中.
>> File.open("hello.txt") do |io| ?> p io.getc >> io.ungetc("h") >> p io.gets >> end "H" "hello,Ruby.\n " => "hello,Ruby.\n " >> exit shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ cat hello.txt Hello,Ruby.
io.getbyte
只读取io中的一个字节,以整数对象返回与得到的字节相对应的ASCII码。数据全部读取完后,再读取时会返回nil
io.ungetbyte(byte)
将参数byte指定的一个字节退出到输入缓冲区中。
io.read(size)
读取参数size中指定的大小的数据。不指定大小时,会一次性读取全部数据并返回
File.open("hello.txt") do |io| p io.read(5) p io.read end
输出操作
io.puts在字符串末尾添加换行符后输出。指定多个参数时,会分别添加换行符。如果参数为String类以外的对象,则会调用to_s方法,将其转换为字符串后再输出
irb(main):015:0> $stdout.puts "String", :Symbol, 1/100r String Symbol 1/100 => nil irb(main):016:0>
io.putc(ch)
输出参数ch指定的字符编码所对应的字符,参数为字符串时输出首字符
RR shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ cat stduout_putc.rb $stdout.putc(82) $stdout.putc("Ruby") $stdout.putc(" ")
剩下还有 io.print, io.printf, io.write
irb(main):001:0> size = $stdout.write("Hello. ") Hello. => 7 irb(main):002:0> p size 7 => 7
io<<str
输出参数str指定的字符串。<<会返回接受者本身,因此可以像下面这样写
io << "foo" << "bar" << "baz"
文件指针
我们用文件指针(file pointer)或者当前文件偏移量(current file offset)来表示IO对象的文件的位置。
io.pos
io.pos =(position)
通过pos方法可以获得文件指针现在的位置。改变文件指针的位置用pos=方法
"Hello" 5 "Hello,Ruby. " shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ cat io_pos.rb File.open("hello.txt") do |io| p io.read(5) p io.pos io.pos = 0 # 初始到开头 p io.gets end
io.seek(offset, whence)
whence用于指定office如何移动
whence中指定的值
IO::SEEK_SET 将文件指针移动到offset指定的位置 那就相当于 io.pos
IO::SEEK_CUR 将offset视为相对于当前位置的偏移量位置来移动文件指针
IO::SEEK_END 将offset指定为行对于文件末尾的偏移文职
io.rewind
"Hello" 0 "Hello,Ruby. " shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ cat io_rewind.rb File.open("hello.txt") do |io| p io.read(5) p io.rewind # 返回到文件的初始位置 ,pos =0 p io.gets end shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$
io.truncate(size)
按照参数size指定的大小截止文件。
io.truncate(0) # 将文件大小设为0
io.truncate(io.pos) # 删除当前文件指针以后的数据
二进制模式与文本模式
ruby默认会在各个平台对换行进行转换,如果不想进行转换,可以将状态切换成二进制模式
File.open("foo.txt", "w") do |io| io.binmode # 进入二进制模式 io.write "Hello, world. " end
缓冲
即使对IO对象进行写入,结果叶不能并不一定马上就反应再控制台或者文件中。再使用write、print等方法操作IO对象时,程序内部会开辟除一定的空间来保存临时生成的数据副本
这部分空间称为缓冲区。缓冲区里积累到一定量的数据后,就会进行输出处理,然后清空缓冲区
使用缓冲区进行数据处理称为缓冲
第1次: 0 第2次: 0 第3次: 0 结束后: 15 "aaaaaaaaaaaaaaa" shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ cat test_buffering1.rb filename = "buffering.txt" File.open(filename, "w") do |file| 3.times do |i| # 检查写入5次字节后的文件的大小 file.write("a" * 5) puts "第#{i+1}次: #{File.size(filename)}" end end puts "结束后: #{File.size(filename)}" p File.read(filename)
这样的好处可以减少频繁的硬盘读写
io.flush
第1次: 5 第2次: 10 第3次: 15 结束后: 15 "aaaaaaaaaaaaaaa" shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$ cat test_buffering2.rb filename = "buffering.txt" File.open(filename, "w") do |file| 3.times do |i| # 检查写入5次字节后的文件的大小 file.write("a" * 5) file.flush # 立即输出缓存区的数据 puts "第#{i+1}次: #{File.size(filename)}" end end puts "结束后: #{File.size(filename)}" p File.read(filename) shijianzhongdeMacBook-Pro:chapter_17 shijianzhong$
io.sync
io.sync=(state)
sync是syncchronize(同步)的意思。设定了这个就不需要io.flush
第1次: 5 第2次: 10 第3次: 15 结束后: 15 "aaaaaaaaaaaaaaa" shijianongdeMBP:chapter_17 shijianzhong$ cat test_buffering3.rb filename = "buffering.txt" File.open(filename, "w") do |file| file.sync = true 3.times do |i| # 检查写入5次字节后的文件的大小 file.write("a" * 5) puts "第#{i+1}次: #{File.size(filename)}" end end puts "结束后: #{File.size(filename)}" p File.read(filename) shijianongdeMBP:chapter_17 shijianzhong$
与命令进行交互
IO.popen(command, mode)
参数 mode的使用方法与File.open方法是一样,参数缺省时默认是"r"模式。
用IO.popen方法生成的IO对象的输入/输出,会关联启动的命令command的标准输入/标准版输出。也就是说,IO对象的输出会作为命令的输入,命令的输出则会作为IO对象的输入
shijianongdeMBP:chapter_17 shijianzhong$ cat simple_grep_gz.rb pattern = Regexp.new(ARGV[0]) filename = ARGV[1] if /.gz$/ =~ filename file = IO.popen("zcat #{filename}") # 指定命令输出io流 else file = File.open(filnename) end file.each_line do |line| # 逐行读取文件 if pattern =~ line print line end end
open("|command", mode)
将带有管道符号的命令传给open方法的效果与使用IO.pepon方法一样
finename = ARGV[0] open("|zcat #{filename}") do |io| io.each_line do |line| print line end end
open-url库
通过require引用open-uri库后,我们可以像打开普通文件一样打开HTTP,FTP的URL。
shijianongdeMBP:chapter_17 shijianzhong$ cat read_url.rb require "open-uri" open("http://www.ruby-lang.org/zh_cn/") do |io| puts io.read # 输出网页信息 end # 通过FTP读取数据 filename = "ruby-2.3.0.tar.gz" url = "ftp://www.ruby-lang.org/pub/ruby/2.3/#{filename}" open(url) do |io| File.binwrite(filename, io.read) # 写入文件 end
stringio库
StringIO就时用于模拟IO对象的修昂。通过require引用stringio库后,就可以使用stringIO对象了
"A B C " shijianongdeMBP:chapter_17 shijianzhong$ cat stringio_puts.rb require "stringio" io = StringIO.new io.puts("A") io.puts("B") io.puts("C") io.rewind p io.read # 进行输出操作
通过将字符串传递给StringIO.new方法的参数,就可以由字符串创建StringIO对象
rld writable dir /usr/local/opt/mysql@5.7/bin in PATH, mode 040777 "A " "B " "C " shijianongdeMBP:chapter_17 shijianzhong$ cat stringio_gets.rb require "stringio" io = StringIO.new("A B C ") p io.gets p io.gets p io.gets
练习题
1 创建脚本,统计文本的行数,统计文本的单词数,统计文本的字符数
shijianzhongdeMacBook-Pro:exercises shijianzhong$ cat e1.rb def count_file(file) lines = File.readlines(file).size word_size = File.read(file).split.size character = File.read(file).size [lines, word_size, character] end p count_file(ARGV[0])
2 创建脚本,将文件中的行逆序排序,保留文件中的第一行数据