Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 4

#9.

1 Streams I/O streams and the IO class ============================ An input/output stream is a sequence of data bytes that are accessed sequentiall y or randomly. This may seem like an abstract, high-level concept -- it is! I/O streams are used to work with almost everything about your computer that you can touch, see, or hear: printing text to the screen receiving key-press input from the keyboard playing sound through speakers sending and receiving data over a network reading and writing files stored on disk All of these listed operations are considered "side-effects" in Computer Science . The touch/see/hear metric doesn't seem to work for network traffic and disk ac tivity but side-effects are not necessarily obvious; in these two cases, somethi ng in the world has physically changed even if you can't see it. Comparatively, "pure" code is code without side-effects: code which simply perfo rms calculations. Of course, a "pure" program isn't very useful if it can't even print its results to the screen! This is where I/O streams come in. Ruby's IO c lass allows you to initialize these streams. Example Code: # open the file "/dev/fd" and create a file descriptor: fd = IO.sysopen("/dev/fd", "w") # create a new I/O stream using the file descriptor for "/dev/fd": p IO.new(fd) fd, the first argument to IO.new, is a file descriptor. This is a Fixnum value w e assign to an IO object. We're using a combination of the sysopen method with I O.new but we can also create IO objects using the BasicSocket and File classes t hat are subclasses of IO. We'll learn more about File in the next lesson. I warned you it would feel a bit abstract! The notion of creating a "file descri ptor" is inherited from UNIX, where everything is a file. Because of this, you c ould use the above technique to open a network socket and send a message to anot her computer. You wouldn't do that, of course -- you would probably use the Basi cSocket (or perhaps TCPSocket) class we just mentioned. Let's leave the abstract behind and find something a little more concrete. There are a bunch of I/O streams that Ruby initializes when the interpreter gets load ed. The list here may seem longer than if you run this locally. These examples a re evaluated in the dense rubymonk environment consisting of Rails, Passenger, a nd all our other magic juice. Example Code: io_streams = Array.new ObjectSpace.each_object(IO) { |x| io_streams << x } p io_streams Standard Output, Input, and Error ================================= Ruby defines constants STDOUT, STDIN and STDERR that are IO objects pointing to your program's input, output and error streams that you can use through your ter

minal, without opening any new files. You can see these constants defined in the list of IO objects we printed out in the last example. Example Code: p STDOUT.class p STDOUT.fileno p STDIN.class p STDIN.fileno p STDERR.class p STDERR.fileno Whenever you call puts, the output is sent to the IO object that STDOUT points t o. It is the same for gets, where the input is captured by the IO object for STD IN and the warn method which directs to STDERR. There is more to this though. The Kernel module provides us with global variable s $stdout, $stdin and $stderr as well, which point to the same IO objects that t he constants STDOUT, STDIN and STDERR point to. We can see this by checking thei r object_id. Example Code: p $stdin.object_id p STDIN.object_id puts p $stdout.object_id p STDOUT.object_id puts p $stderr.object_id p STDERR.object_id As you can see, the object_ids are consistent between the global variables and c onstants. Whenever you call puts, you're actually calling Kernel.puts (methods i n Kernel are accessible everywhere in Ruby), which in turn calls $stdout.puts. So why all the indirection? The purpose of these global variables is temporary r edirection: you can assign these global variables to another IO object and pick up an IO stream other than the one that it is linked to by default. This is some times necessary for logging errors or capturing keyboard input you normally woul dn't. Most of the time this won't be necessary... but hey, it's cool to know you can do it! We can use the StringIO class to easily fake out the IO objects. Try to capture STDERR so that calls to warn are redirected to our custom StringIO object. capture = StringIO.new $stderr = capture # 9.2 Using the `File` Class Opening and closing =================== Where we used IO.sysopen and IO.new to create a new IO object in the last lesson , we'll use the File class here. You'll notice it's much more straight-forward! (Note that file.inspect will return a FakeFS::File -- this isn't a real File obj ect because otherwise your "friend-list.txt" would conflict with other rubymonk users' "friends-list.txt". Don't worry -- it behaves just like a real File objec t. See for yourself! File#read is shown as an example.) Example Code:

mode = "r+" file = File.open("friend-list.txt", mode) puts file.inspect puts file.read file.close mode is a string that specifies the way you would like your file to be opened. H ere we're using r+, which opens the file in read-write mode, starting from the b eginning. w opens it in write-only mode, truncating the existing file. You can t ake look at all the possible modes [here](http://ruby-doc.org/core-1.9.3/IO.html ). It's worth noting that there are (many!) multiple ways of opening files in Ruby. File.open also takes an optional block which will auto-close the file you opene d once you are done with it. Example Code: what_am_i = File.open("clean-slate.txt", "w") do |file| file.puts "Call me Ishmael." end p what_am_i File.open("clean-slate.txt", "r") {|file| puts file.read } Reading and writing =================== Now we'll take a look at some methods to read from an I/O stream. In these examp les, our I/O stream is a file, but remember: as described in the previous lesson , files behave just like any other I/O stream. The File#read method accepts two optional arguments: length, the number of bytes upto which the stream will be read, and buffer, where you can provide a String buffer which will be filled with the file data. This buffer is sometimes useful for performance when iterating over a file, as it re-uses an already initialized string. Example Code: file = File.open("master", "r+") p file.read file.rewind # try commenting out this line to see what happens! # can you guess why this happens? buffer = "" p file.read(23, buffer) p buffer file.close Could you guess why we had to use File#rewind before the second call to File#rea d? It's okay if you couldn't or weren't sure. The reason is actually due to Ruby 's internal handling of files. When reading from a File object, Ruby keeps track of your position. In doing so, you could read a file one line (or page, or arbi trary chunk) at a time without recalculating where you left off after the last r ead. If this still isn't clear, try changing the first file.read to file.read(16) and then comment out the file.rewind line again. The position in the file should be obvious from the second read. File#seek should solidify this idea even further. You can "seek" to a particular byte in the file to tell Ruby where you want to start reading from. If you want a particular set of bytes from the file, you can then pass the length parameter to File#read to select a number of bytes from your new starting point.

Example Code: p File.read("monk") File.open("monk") do |f| f.seek(20, IO::SEEK_SET) p f.read(10) end readlines returns an array of all the lines of the opened IO stream. You can, ag ain, optionally limit the number of lines and/or insert a custom separator betwe en each of these lines. Example Code: lines = File.readlines("monk") p lines p lines[0] To write to an I/O stream, we can use IO#write (or, in our case, File#write) and pass in a string. It returns the number of bytes that were written. Try calling the method that writes "Bar" to a file named disguise. File.open("disguise", "w") do |f| f.write "Bar" end

You might also like