PerlerのRuby日記

Rubyとか

配列の要素が、1ずつ増加しているかどうかをチェックする

配列内の要素が1ずつ増えているかどうかをチェックしたかったメモ。

[1, 2, 3, 4] #=> OK
[1, 2, 4]    #=> NG
[4, 5]       #=> OK
[6]          #=> OK
[7, 8, 0]    #=> NG

今回自分が出くわした場面では、要素内の値が0〜9ということが確定していたので、こんな解決方法を思いついた。

ary = [1, 2, 3, 4]

ok_str = (0..9).to_a.join #=> "0123456789"
if ok_str.index(ary.join)
  puts "OK!"
else
  puts "NG."
end

あらかじめ0から9までの数字を連結させた文字列を用意して、String#indexでひっかけるやり方である。

まあでもかなり特殊な条件だし、ぱっと見て何をしているか親切ではないので、何かないですかね〜とチームメイトに相談したら、Array#each_consというのを教えてもらった。

ary = [1, 2, 3, 4]

ary.each_cons(2) do |v1, v2|
  puts "v1: #{v1}, v2: #{v2}"
end

#=> v1: 1, v2: 2
#=> v1: 2, v2: 3
#=> v1: 3, v2: 4

https://ruby-doc.org/core-2.5.0/Enumerable.html#method-i-each_cons

順繰りにまとめたものをループしてくれる。すばらしい。

なので、0〜9とかに限らず、ちゃんと1ずつ増加しているかどうかを確認するやり方をeach_consを使ってみると、

ary = [1, 2, 3, 4]

result = ary.each_cons(2) do |v1, v2|
  if (v1 + 1) != v2
    break false
  end
end

if result.nil?
  puts "OK!"
elsif result == false
  puts "NG."
end

こんな感じかな。each_consは正常に回りきったらnilを返してくれるので、途中でだめだったときにそれ以外の値でbreakして切り分けるのがちょっとかっこ悪いけど、これなら0〜100とか300〜89654とか、桁数に限らずチェックはできそう。

privateなセッタメソッドはself.をつけて呼ばなければならない

Rubyのprivateなセッタメソッドについてメモ。

Rubyのprivateメソッドはレシーバを省略しないと呼べない仕様だけど、
末尾に「=」がつくセッタメソッドに限ってはself付きで呼ばないと動いてくれないようだ。

class Foo
  def foo
    bar=("bar=")           # NG(ローカル変数代入扱いになる)
    self.bar=("self.bar=") # OK

    bar("bar")             # OK
    self.bar("self.bar")   # Error
  end

  private

  def bar(i)
    puts i
  end

  def bar=(i)
    puts i
  end
end

Foo.new.foo
self.bar=
bar
aaa.rb:7:in `foo': private method `bar' called for #<Foo:0x00007ff5e3930270> (NoMethodError)
	from aaa.rb:21:in `<main>'

Kernel.openにパイプを渡す

Ruby2.4.3のリリースに、Net::FTP脆弱性の修正をした、というのを見た。

CVE-2017-17405: Net::FTP におけるコマンドインジェクションの脆弱性について

ローカルのファイルを開くために、それぞれ内部で Kernel#open を使用しています。しかし、もし localfile 引数がパイプ文字 "|" で開始されていた場合、パイプ文字以降に並べられたコマンドが実行されてしまいます。

module function Kernel.#open (Ruby 2.4.0)

ファイル名 file が `|' で始まる時には続く文字列をコマンドとして起動し、コマンドの標準入出力に対してパイプラインを生成します

ファイル名が "|-" である時、open は Ruby の子プロセス を生成し、その子プロセスとの間のパイプ(IOオブジェクト)を返します。(このときの動作は、IO.popen と同じです。File.open にはパイプラインを生成する機能はありません)。

ファイル名の代わりに、 "|" を先頭につけたものを渡せば、パイプとかforkとかできるらしい。そういえばPerlでもこんなのあったわ。

やってみたメモ

パイプから読み取り

foo.txt

aaaaaa
bbbbbb
cccccc

pipe_r.rb

io = Kernel.open("| cat foo.txt")

# 愚直に3回呼ぶ
#puts io.gets
#puts io.gets
#puts io.gets

# 一気読みする
#puts io.readlines

# 一行ずつ読む
io.each do |line|
  puts line
end

io.close
$ ruby pipe_r.rb
aaaaaa
bbbbbb
cccccc
パイプへ書き込み

pipe_w.rb

io = Kernel.open("| cat", "w")
io.puts("Yeeeees!")
io.close
$ ruby pipe_w.rb
Yeeeees!
forkしてやりとり (IO.#popenのマニュアルのサンプルそのまま)

fork.rb

io = Kernel.open("|-", "r+")
if io  # parent
  io.puts "foo"
  puts io.gets
  io.close
else   # child
  s = gets
  print "child output: " + s
  exit
end
$ ruby fork.rb
child output: foo


使う機会があるかはわからないが、一応まとめてみた。