我有一个从$stdin
读取的应用程序,并对数据进行了一些处理。我想放入一个信号处理程序来捕获SIGINT / SIGTERM并正常关闭(意味着完成处理数据并在完成后退出)。棘手的部分是我希望它停止从STDIN读取,但能够处理任何缓冲的数据。这样就可以启动另一个应用程序并传递相同的STDIN管道,并恢复前一个应用程序停止的处理。
问题在于,如果我关闭STDIN,那么缓冲的内容就会丢失,或者至少无法访问。
基本上我正在尝试这个:
#!/usr/bin/ruby
Signal.trap('INT') do
$stdin.close
end
f = File.open('/tmp/out', 'a')
while (data = $stdin.read(4096)) != "" do
f.write(data)
end
它立即在IOError
调用上发出$stdin.read
个异常,即使我知道它读取了一些数据(strace显示它)。
(我不需要关闭管道,我只是为了打破while
循环。如果有更优雅的方法来打破循环并获取缓冲数据,我我乐意接受它。)
我知道这种方法适用于操作系统级别(管道缓冲区在传递给另一个应用程序时保留),因为我可以进行以下测试,并且没有数据丢失:
# source.rb
i = 0
loop do
puts "%08d" % (i += 1)
end
# reader.rb
$stdout.write($stdin.read(9))
$stdin.close
ruby /tmp/source.rb | while true; do ruby reader.rb; sleep 1; done
00000001
00000002
00000003
00000004
00000005
答案 0 :(得分:0)
解决此问题的一种方法是在关闭原始文件之前复制文件描述符,然后错误将中断循环,并且您可以从未关闭的重复文件句柄中读取其余数据。
(对不起,如果这段代码不好,我不知道ruby)
#!/usr/bin/ruby
require 'fcntl'
stdin_dup = nil
Signal.trap('INT') do
stdin_dup = File.for_fd($stdout.fcntl(Fcntl::F_DUPFD))
$stdin.close
end
f = File.open('/tmp/out', 'a')
begin
while (data = $stdin.read(4096)) != "" do
f.write(data)
end
rescue IOError
# finish stuff with stdin_dup here
end
答案 1 :(得分:0)
经过几天的挣扎,我最终不得不放弃IO.read
而是使用IO.sysread
来做自己的缓冲。这个解决方案真的不复杂,下面是实现。
Signal.trap('INT') do
$stdin.close
end
def myread(bufio, bytes) # `bufio` is a StringIO object, `bytes` is bytes to read
begin
while bufio.size < bytes do
bufio.write($stdin.sysread(bytes - bufio.size))
end
rescue SignalException, Interrupt, Errno::EINTR => e
retry
rescue SystemCallError, IOError, EOFError => e
# nothing, we're done
end
end
我的确切代码与我使用AWS ruby SDK的情况略有不同,因此myread
方法实际上只是传递给AWS::S3::S3Object.write
的块