Created
April 20, 2009 19:25
-
-
Save rklemme/98693 to your computer and use it in GitHub Desktop.
Command line tool 'tee'.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby19 | |
require 'optparse' | |
# Report erros that are caused by invoking a method when | |
# an instance is not in the proper state to do so. | |
class StateError < Exception | |
end | |
# Instances of class Tee can send output to several destinations | |
# at the same time much like the command line utility 'tee'. | |
# Every IO stream that reports an error is silently closed and | |
# not written to any more. | |
class Tee | |
# Open the given files for writing in the given mode | |
# (see #initialize). If the mode is omitted it defaults | |
# to "w" (overwrite). There are two operation modes of | |
# this method: | |
# * When invoked with a block the tee | |
# instance is yielded to that block and closed when the | |
# block is terminated. In this case return value is result | |
# of the block evaluation. | |
# * Without a block the newly created and opened Tee instance | |
# is returned | |
def self.open(file_names, mode = "w") | |
tee = new(file_names, mode) | |
tee.open | |
if block_given? | |
begin | |
yield tee | |
ensure | |
tee.close | |
end | |
else | |
tee | |
end | |
end | |
# Initializes a Tee instance with a list of file names and | |
# an IO mode which must be any of "w", "wb", "a" or "ab". | |
def initialize(file_names, mode) | |
raise ArgumentError, "Wrong mode %p" % mode unless /\A[wa]b?\z/i =~ mode | |
@file_names = file_names | |
@mode = mode | |
end | |
# Opens all the files for writing. If files are opened | |
# already nothing happens. | |
# returns self | |
def open | |
raise StateError, "Already open" if @ios | |
@ios = @file_names.map do |name| | |
File.open(name, @mode) rescue nil | |
end.compact | |
self | |
end | |
# All files are closed. If they are not opened this is | |
# a nop. | |
def close | |
if @ios | |
@ios.each {|io| io.close} | |
@ios = nil | |
end | |
end | |
# Invokes 'puts' on all streams with the given arguments. | |
# returns self. | |
def puts(*args) delegate(:puts, args) end | |
# Writes the given argument to all streams via IO#write. | |
# returns self. | |
def write(str) delegate(:write, str) end | |
# Appends argument to all files via IO#<<. | |
# returns self. | |
def <<(arg) delegate(:<<, arg) end | |
private | |
# Send the given method with the given arguments | |
# to all IO instances. Every instance which has | |
# errors is closed and removed from the list. | |
def delegate(meth, args) | |
@ios.delete_if do |io| | |
ex = nil | |
begin | |
io.send(meth, *args) | |
rescue SystemCallError => ex | |
io.close rescue nil | |
end | |
ex | |
end | |
self | |
end | |
end | |
# start of MAIN | |
file_mode = "w" | |
OptionParser.new do |opts| | |
opts.on "-a", "--append", | |
"Appends to files instead of overwriting them" do | |
file_mode = "a" | |
end | |
opts.on_tail "-h", "--help", | |
"Print this help" do | |
puts opts | |
exit 0 | |
end | |
end.parse! ARGV | |
Tee.open ARGV, file_mode do |tee| | |
$stdin.each do |line| | |
tee.puts(line) | |
$stdout.puts(line) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment