Ruby での
外部コマンドの実行について
2019/04/10
大手町.rb #16
大手町.rb #16 「Ruby での外部コマンドの実行について」
1自己紹介
Tomoya Kawanishi a.k.a. @cuzic
エネチェンジ株式会社 チーフエンジニア
電力会社、ガス会社を切り替えるなら、エネチェンジ経由で!
一般家庭も!法人も!
Ruby関西の中の人
2019年5月11日 第86回Ruby関西勉強会
2019年7月13日 第87回Ruby関西勉強会
発表者として登壇くださる方、あとで声かけください。
大手町.rb の中の人
毎月 大手町.rb の開催を予定
東京駅、各線大手町駅から直結!
Ruby の初級者がメインターゲット
大手町.rb #16 「Ruby での外部コマンドの実行について」
今日のテーマ
Ruby での外部コマンドの実行について
外部コマンド
ruby では system コマンド
echo 、time、ruby などすべて外部コマンド
Ruby はグルー言語としてとても優秀
グルー = のり(糊)
いろんな処理をくっつけて、新しい処理を組み立てるときに
使う言語を グルー言語 と呼ぶ
すでにいろんなベンリな機能がたくさんあるわけだし、
グルー言語として、実現する方がカンタンかつ速い!
2
大手町.rb #16 「Ruby での外部コマンドの実行について」
` ` (backtick)、 %x()、%X()
`` 、%x で簡単に外部コマンドの実行結果が取得可能
3
`aws configure get aws_access_key_id`.chomp
#=> "AKIA...........A"
`git config user.name`.chomp
#=> "cuzic"
# `` と %x は式展開する
field = "user.name"
`echo #{field}"`.chomp
#=> "user.name"
%x(echo #{field}").chomp
#=> "user.name"
# %X だと式展開しない
%X(echo #{field}").chomp
#=> "#{field}"
大手町.rb #16 「Ruby での外部コマンドの実行について」
Kernel.system
外部コマンドを実行し、成功したときに真が返る
4
system("echo Hello, World!") # PATH で見つかる echo を呼ぶ
system("echo /dev/z*") # ワイルドカードも書ける。シェル経由で実行される。
system('echo –n a¥nb') # /bin/echo は bash の echo と挙動が異なる。
# 成功するときは、true が返る
system("true") #=> true
# 失敗するときは false が返る
system("false") #=> false
# ; 区切りで複数コマンド実行できる
system("echo 1 ; echo 2")
# 改行区切りでも複数コマンド実行できる
system("echo 1 ¥n echo 2")
# 該当のコマンドが実行できないときは nil が返る
system("sad") #=> nil
大手町.rb #16 「Ruby での外部コマンドの実行について」
system の高度な利用 5
# 配列で引数を渡すこともできる
system("ssh", hostname, "bash –l –c 'cd repo; bin/rails c'")
# exception: true を指定すると、 nil が返るのではなく例外を raiseする
system("sad", exception: true) #=> Errno::ENOENT (No such file or
directory - sad)
# unsetenv_others: true を指定すると、環境変数をクリアする
system("echo $HOME") # "/home/cuzic" などが表示される
system("echo $HOME", unsetenv_others: true) # 何も表示されない
# bundle exec の中で ruby コマンドを実行するときとか便利
# 第1引数で環境変数を指定できる
system({"FOO"=>"BAR"}, "echo $FOO")
# chdir オプションで子プロセスのカレントディレクトリを変更できる
# "cd /tmp; pwd" とすると、シェル経由で実行されるが、
# 下記はシェルを経由せず、実行される
system("pwd", chdir: "/tmp")
大手町.rb #16 「Ruby での外部コマンドの実行について」
Kernel.exec
exec で実行すると ruby インタプリタを置き換える
ruby で ssh を wrap したい時とかベンリ
6
STDIN.close
STDOUT.close
system("ssh production-server")
# `system': closed stream (IOError)
STDIN.close
STDOUT.close
exec("ssh production-server")
# ふつうに ssh できる!
system("ssh production-server")
puts "ssh finished" # ssh 終了後、実行、表示される
exec("ssh production-server")
puts "ssh finished" # ssh 終了してもこの行は決して実行されない
大手町.rb #16 「Ruby での外部コマンドの実行について」
shellwords
Array#shelljoin 、String#shellescape がベンリ
7
# system メソッドに配列で引数を渡す
system("ssh", hostname, "bash –l –c 'cd repo; bin/rails version'")
# String#shellescape
rails_version = "bash –l –c 'cd repo; bin/rails version'"
require 'shellwords'
rails_version.shellescape
#=> "bash¥¥ ¥¥–l¥¥ ¥¥–c¥¥ ¥¥'cd¥¥ repo¥¥;¥¥ bin/rails¥¥ version¥¥'"
# Array#shelljoin
cmdline = ["ssh", hostname, rails_version].shelljoin
#=> "ssh production-server bash¥¥ ¥¥–l¥¥ ¥¥–c¥¥ ¥¥'cd¥¥ repo¥¥;¥¥ bin/rails¥¥ version¥¥'"
# system メソッドに文字列で渡す
`#{cmdline}`
#=> "Rails 5.0.7¥n"
大手町.rb #16 「Ruby での外部コマンドの実行について」
IO.popen
IO.popen で、標準入力からの値の取得ができる
エンコーディングの指定もできる
標準エラー出力も受け取れる
8
env = {"PGUSER"=>"scott", "PGPASSWORD"=>"tiger"}
body = IO.popen(env, "psql template1", "r+") do |io|
io.puts '¥dS+'
io.close_write
io.read
end
puts body
IO.popen("nkf –s README.md", external_encoding: "CP932", &:read)
IO.popen("ls /nothing", err: %i(child out), &:read)
#=> "ls: /nothing にアクセスできません: そのようなファイルやディレクトリはあり
ません¥n"
大手町.rb #16 「Ruby での外部コマンドの実行について」
Open3.capture3
標準入力への入力、標準出力、標準エラー出力の取得が
一度にできてとてもベンリ!
環境変数の設定などもできる
9
require 'open3'
env = {"PGUSER"=>"scott", "PGPASSWORD"=>"tiger"}
sql = <<'EOD'
¥d
selct 1 + 1;
EOD
options = {stdin_data: sql}
o, e, s = Open3.capture3(env, "psql", "template1", options)
p o #=> "リレーションがありません。¥n"
p e #=> "ERROR: ¥"selct¥"またはその近辺で構文エラー¥n行 1:"
p s #=> #<Process::Status: pid 21358 exit 0>
select を typo してる!
大手町.rb #16 「Ruby での外部コマンドの実行について」
expect
PTY と expect でインタラクティブ操作も自動化できる
非常に多数の L2SW やルータを自動制御したいときとか便利
10
require 'pty'
require 'expect'
username, password, database = "scott", "tiger", "template1"
PTY.getpty("psql –U #{username} #{database}") do |i, o|
o.sync = true
i.expect(/パスワード:/) do
o.puts password
i.expect(/#{database}/) do
o.puts "¥¥d"
i.expect(/#{database}/) do |result|
puts result
end
end
end
end
大手町.rb #16 「Ruby での外部コマンドの実行について」
まとめ
Ruby での外部コマンドの実行方法についていろいろ紹介
しました
開発生産性を改善するには
・あるものは使う。つなぐ
というのが大切です
Rubyはいろんなライブラリが用意されているので
どんな状況でもたいてい自動化することができます
今日の説明が今後の開発にお役立ちできるとうれしいです
11
ご清聴ありがとう
ございました

Ruby での外部コマンドの実行について

  • 1.
  • 2.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 1自己紹介 Tomoya Kawanishi a.k.a. @cuzic エネチェンジ株式会社 チーフエンジニア 電力会社、ガス会社を切り替えるなら、エネチェンジ経由で! 一般家庭も!法人も! Ruby関西の中の人 2019年5月11日 第86回Ruby関西勉強会 2019年7月13日 第87回Ruby関西勉強会 発表者として登壇くださる方、あとで声かけください。 大手町.rb の中の人 毎月 大手町.rb の開催を予定 東京駅、各線大手町駅から直結! Ruby の初級者がメインターゲット
  • 3.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 今日のテーマ Ruby での外部コマンドの実行について 外部コマンド ruby では system コマンド echo 、time、ruby などすべて外部コマンド Ruby はグルー言語としてとても優秀 グルー = のり(糊) いろんな処理をくっつけて、新しい処理を組み立てるときに 使う言語を グルー言語 と呼ぶ すでにいろんなベンリな機能がたくさんあるわけだし、 グルー言語として、実現する方がカンタンかつ速い! 2
  • 4.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 ` ` (backtick)、 %x()、%X() `` 、%x で簡単に外部コマンドの実行結果が取得可能 3 `aws configure get aws_access_key_id`.chomp #=> "AKIA...........A" `git config user.name`.chomp #=> "cuzic" # `` と %x は式展開する field = "user.name" `echo #{field}"`.chomp #=> "user.name" %x(echo #{field}").chomp #=> "user.name" # %X だと式展開しない %X(echo #{field}").chomp #=> "#{field}"
  • 5.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 Kernel.system 外部コマンドを実行し、成功したときに真が返る 4 system("echo Hello, World!") # PATH で見つかる echo を呼ぶ system("echo /dev/z*") # ワイルドカードも書ける。シェル経由で実行される。 system('echo –n a¥nb') # /bin/echo は bash の echo と挙動が異なる。 # 成功するときは、true が返る system("true") #=> true # 失敗するときは false が返る system("false") #=> false # ; 区切りで複数コマンド実行できる system("echo 1 ; echo 2") # 改行区切りでも複数コマンド実行できる system("echo 1 ¥n echo 2") # 該当のコマンドが実行できないときは nil が返る system("sad") #=> nil
  • 6.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 system の高度な利用 5 # 配列で引数を渡すこともできる system("ssh", hostname, "bash –l –c 'cd repo; bin/rails c'") # exception: true を指定すると、 nil が返るのではなく例外を raiseする system("sad", exception: true) #=> Errno::ENOENT (No such file or directory - sad) # unsetenv_others: true を指定すると、環境変数をクリアする system("echo $HOME") # "/home/cuzic" などが表示される system("echo $HOME", unsetenv_others: true) # 何も表示されない # bundle exec の中で ruby コマンドを実行するときとか便利 # 第1引数で環境変数を指定できる system({"FOO"=>"BAR"}, "echo $FOO") # chdir オプションで子プロセスのカレントディレクトリを変更できる # "cd /tmp; pwd" とすると、シェル経由で実行されるが、 # 下記はシェルを経由せず、実行される system("pwd", chdir: "/tmp")
  • 7.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 Kernel.exec exec で実行すると ruby インタプリタを置き換える ruby で ssh を wrap したい時とかベンリ 6 STDIN.close STDOUT.close system("ssh production-server") # `system': closed stream (IOError) STDIN.close STDOUT.close exec("ssh production-server") # ふつうに ssh できる! system("ssh production-server") puts "ssh finished" # ssh 終了後、実行、表示される exec("ssh production-server") puts "ssh finished" # ssh 終了してもこの行は決して実行されない
  • 8.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 shellwords Array#shelljoin 、String#shellescape がベンリ 7 # system メソッドに配列で引数を渡す system("ssh", hostname, "bash –l –c 'cd repo; bin/rails version'") # String#shellescape rails_version = "bash –l –c 'cd repo; bin/rails version'" require 'shellwords' rails_version.shellescape #=> "bash¥¥ ¥¥–l¥¥ ¥¥–c¥¥ ¥¥'cd¥¥ repo¥¥;¥¥ bin/rails¥¥ version¥¥'" # Array#shelljoin cmdline = ["ssh", hostname, rails_version].shelljoin #=> "ssh production-server bash¥¥ ¥¥–l¥¥ ¥¥–c¥¥ ¥¥'cd¥¥ repo¥¥;¥¥ bin/rails¥¥ version¥¥'" # system メソッドに文字列で渡す `#{cmdline}` #=> "Rails 5.0.7¥n"
  • 9.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 IO.popen IO.popen で、標準入力からの値の取得ができる エンコーディングの指定もできる 標準エラー出力も受け取れる 8 env = {"PGUSER"=>"scott", "PGPASSWORD"=>"tiger"} body = IO.popen(env, "psql template1", "r+") do |io| io.puts '¥dS+' io.close_write io.read end puts body IO.popen("nkf –s README.md", external_encoding: "CP932", &:read) IO.popen("ls /nothing", err: %i(child out), &:read) #=> "ls: /nothing にアクセスできません: そのようなファイルやディレクトリはあり ません¥n"
  • 10.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 Open3.capture3 標準入力への入力、標準出力、標準エラー出力の取得が 一度にできてとてもベンリ! 環境変数の設定などもできる 9 require 'open3' env = {"PGUSER"=>"scott", "PGPASSWORD"=>"tiger"} sql = <<'EOD' ¥d selct 1 + 1; EOD options = {stdin_data: sql} o, e, s = Open3.capture3(env, "psql", "template1", options) p o #=> "リレーションがありません。¥n" p e #=> "ERROR: ¥"selct¥"またはその近辺で構文エラー¥n行 1:" p s #=> #<Process::Status: pid 21358 exit 0> select を typo してる!
  • 11.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 expect PTY と expect でインタラクティブ操作も自動化できる 非常に多数の L2SW やルータを自動制御したいときとか便利 10 require 'pty' require 'expect' username, password, database = "scott", "tiger", "template1" PTY.getpty("psql –U #{username} #{database}") do |i, o| o.sync = true i.expect(/パスワード:/) do o.puts password i.expect(/#{database}/) do o.puts "¥¥d" i.expect(/#{database}/) do |result| puts result end end end end
  • 12.
    大手町.rb #16 「Rubyでの外部コマンドの実行について」 まとめ Ruby での外部コマンドの実行方法についていろいろ紹介 しました 開発生産性を改善するには ・あるものは使う。つなぐ というのが大切です Rubyはいろんなライブラリが用意されているので どんな状況でもたいてい自動化することができます 今日の説明が今後の開発にお役立ちできるとうれしいです 11
  • 13.