【问题标题】:How to use $stdout and $stdin with unix pipes in Ruby?如何在 Ruby 中将 $stdout 和 $stdin 与 unix 管道一起使用?
【发布时间】:2014-10-31 15:39:18
【问题描述】:

我可以像这样创建一个 ruby​​ 可执行文件(虚拟示例):

echo '#!/usr/bin/env ruby
    puts %x[awk ''{print toupper($1)}'' #{STDIN} ]
' > pipes.rb
chmod +x pipes.rb

然后我可以使用这个文件其他 unix 工具:

echo "a\nb\nc\nd" | ./pipes.rb | head -n2
# A
# B

但如果我需要通过另一个对 awk 的调用来扩展虚拟示例,则它不起作用:

echo '#!/usr/bin/env ruby
    puts %x[awk ''{print toupper($1)}'' #{STDIN} | awk ''{ \
    print tolower($1) \
}'']
' >! pipes2.rb
chmod +x pipes2.rb
echo "a\nb\nc\nd" | ./pipes2.rb | head -n2
# A
# B
# it should be: "a\nb"

问题是STDIN 被字符串化为:#<IO:0x007fe18406ac58> 并且哈希被解释为注释,因此第二个 awk 语句被忽略(但由于某种原因,head 命令仍然返回两个行):

awk '{print toupper($1)}' #<IO:0x007fe18406ac58> | awk '{ print tolower($1)}'

我确信有更好的方法来做到这一点(转义 STDIN 参考?)。这是我能想到的最简单的可重现示例。在我的真实脚本中,我允许多个输入源(标准输入或文件参数)。不可协商的要求是 awk 代码需要对输入的引用,我不能在 Ruby 中逐行处理它。

有什么想法吗?

更新 按照@tadman 的建议,我已经做到了,并且成功了!:

#!/usr/bin/env ruby
require "open3"
Open3.popen3("awk '{print toupper($1)}'") do |cmd_in, cmd_out, cmd_err|
    cmd_in.write(STDIN.read)
    cmd_in.close
    Open3.popen3("awk '{print tolower($1)}'") do |cmd_in2, cmd_out2, cmd_err2|
        cmd_in2.write(cmd_out.read)
        cmd_in2.close
        puts cmd_out2.read    
    end
end


echo "aAA\nbBB\ncCC\ndDD" | ./pipes2.rb | head -n2
# aaa
# bbb

有没有办法重构这个?

【问题讨论】:

  • 在生产 Ruby 代码中看到 $stdin$stdout 有点不寻常,它们是受 Perl 启发的全局变量。大多数时候你会看到STDINSTDOUT 被使用。 STDOUT.puts 也是多余的,因为默认情况下 puts 会转到 STDOUT
  • 好的,我可以使用STDINputs,但是结果是一样的。

标签: ruby awk pipe stdout stdin


【解决方案1】:

这里的问题是你直接提供$stdin 而不是用它做任何事情。这就是 Ruby 将其渲染为原始对象的原因。它是一个文件句柄,除非您对其执行方法,而不是原始数据。

您想要的是从该文件句柄中获取所有内容:

$stdin.read

如果你打算使用 Ruby,没有理由使用awk

#!/usr/bin/env ruby

puts STDIN.read.upcase

如果您希望将换行符保留为"\n",请执行以下操作:

puts STDIN.read.upcase.inspect

如果您致力于使用外部命令:

require 'open3'

Open3.popen3("awk '{print toupper($1)}'") do |cmd_in, cmd_out, cmd_err|
  # Read from our STDIN and push through to the command's STDIN
  cmd_in.write(STDIN.read)

  # Close STDIN on the command to tell it we're finished writing.
  cmd_in.close

  # Read result from command's STDOUT and write to our STDOUT
  puts cmd_out.read.inspect
end

【讨论】:

  • 谢谢,但我确实需要使用 awk。大写/小写只是一个例子
  • 确定你需要使用awk吗?它没有什么是 Ruby 做不到的,而且从 awk 转换为 Ruby 通常非常简单。如果您确实需要使用外部命令,popen3 就是这样做的方法,让您可以完全控制输入和输出。
  • 看起来很有趣,有没有办法组合多个命令(如上面的两个 awk 调用)?
  • 当然,您可以根据需要不断更改 in->in、out->out,甚至在需要时嵌套它们。
【解决方案2】:

好的,我找到了更简单的方法

require "open3"
Open3.pipeline(
    ["awk '{print toupper($1)}'"],
    ["awk '{print tolower($1)}'"], 
:in => STDIN) # this is redundant, but I might want to change :in in the future

【讨论】:

    猜你喜欢
    • 2017-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 2011-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多