14
10

More than 5 years have passed since last update.

Chefの`shell_out`と`shell_out!`、違いと使い分け

Last updated at Posted at 2014-04-01

この記事は最終更新から1年以上経過しています。 気をつけてね。

Chef::Mixin::ShellOut

Chef::Mixin::ShellOutはプラットフォームにあったCLI(Unix=>Shell, Windows=>cmd)を呼び出して、コマンドを実行するライブラリです。
コマンドをフォークしたプロセスで実行し、ExitStatusと標準出力/標準エラー出力を使いやすい形にしてくれることが特徴で、モックなども書きやすいため、ChefのCookbook内ではsystemなどのrubyメソッドよりShellOutを使用することが推奨されています。

ちなみに継承元はopscode/mixlib-shelloutです。これも単体で使用できますが、Chef::Mixin::ShellOutとは少し使い勝手が違います。

shell_out

では早速つかってみましょう。

Pry_Console
> require 'chef'
=> true

> include Chef::Mixin::ShellOut
=> Object

> result = shell_out('pwd')
=> <Mixlib::ShellOut#23553880:
  command: 'pwd'
  process_status: #<Process::Status: pid 16170 exit 0>
  stdout: '/root'
  stderr: ''
  child_pid: 16170
  environment: {"LC_ALL"=>"C"}
  timeout: 600
  user:
  group:
  working_dir:
>

コマンドの実行結果はMixlib::ShellOutのインスタンスとなり、結果にまつわるアレコレを取得することができます。

Pry_Console
> result.class
=> Mixlib::ShellOut

> result.stdout
=> "/root\n"

> result.exitstatus
=> 0

コマンドの実行結果ステータスが0以外でも大丈夫。

Pry_Console
> result = shell_out('ps | grep hogehgoe')
=> <Mixlib::ShellOut#27543860:
  command: 'ps | grep hogehgoe'
  process_status: #<Process::Status: pid 16313 exit 1>
  stdout: ''
  stderr: ''
  child_pid: 16313
  environment: {"LC_ALL"=>"C"}
  timeout: 600
  user:
  group:
  working_dir:
>

> result.exitstatus
=> 1

shell_out!

shell_out!shell_outと使い方は一緒ですが、exitstatusが

Pry_Console
> result = shell_out!('ps | grep hogehgoe')
Mixlib::ShellOut::ShellCommandFailed: Expected process to exit with [0], but received '1'
---- Begin output of ps | grep hogehgoe ----
STDOUT: 
STDERR: 
---- End output of ps | grep hogehgoe ----
Ran ps | grep hogehgoe returned 1
from /opt/chef/embedded/lib/ruby/gems/1.9.1/gems/mixlib-shellout-1.2.0/lib/mixlib/shellout.rb:251:in `invalid!'

Rubyではしばしば!が付くメソッドは自身を変更する破壊的メソッドをあわらす記号として使われます。
shell_out!もまあ、例外終了するという理由で破壊的な挙動と覚えておくといいかもしれません。

0以外の終了ステータスでshell_out!

ExitCodeが0以外であることが要求されるケースでは、:returnsオプションで求める結果を指定することができます。

> result = shell_out!('ps | grep hogehgoe', :returns => 1)
=> <Mixlib::ShellOut#18867260:
  command: 'ps | grep hogehgoe'
  process_status: #<Process::Status: pid 16586 exit 1>
  stdout: ''
  stderr: ''
  child_pid: 16586
  environment: {"LC_ALL"=>"C"}
  timeout: 600
  user: 
  group: 
  working_dir: 
>

## ArrayでもOK
> result = shell_out!('ps | grep hogehgoe', :returns => [1,3,5,7,9])

その他使えるオプションはソースを見るとよいです。
mixlib-shellout/lib/mixlib/shellout.rb at master · opscode/mixlib-shellout

プライベートメソッドの#parse_optionsが使えるオプション一覧です。

使い分け方

shell_outが必要なケース

実行結果のうち、ステータスや出力を使用して処理をしたい時にこちらを使います。LWRP中でのCurrentResource収集の一貫としてなどですね。
RecipeやLibrary内でも標準出力から文字列を組み立てたい時等に使うとよいでしょう。

shell_out!が必要なケース

CookbookではLWRPが適当ですが、収束をかける処理の一環として、そのコマンドが期待した結果を得られなかったらリソースが収束しないというケースで使用します。
shell_outで、ExitStatusが0でなければfailするという分岐を作ることと同様ですが、よりシンプルに記述することができます。

Command not Foundは流石にダメ

shell_outが実行結果の成否にかかわらないといえ、実行するコマンド自体へパスが通ってなかったり、そもそも存在しないという状況は例外で終了します。

> shell_out('hogehgoe')
Errno::ENOENT: No such file or directory - hogehgoe
from /opt/chef/embedded/lib/ruby/gems/1.9.1/gems/mixlib-shellout-1.2.0/lib/mixlib/shellout/unix.rb:268:in `exec'

これは素直に段取りをちゃんとしましょう。

14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10