Elixirコードから外部コマンドを直接叩けると便利な場合があります。
System.cmd/3
引数
-
PATH
で使用可能な実行可能なファイル名、またはコマンドの絶対パス - コマンドがそのまま引数として受け取れる文字列のリスト
戻り値
早速IExを開きます。
iex
コマンドに渡す引数がある場合
echo "元氣"
をElixirから叩いてみます。
iex> System.cmd "echo", ["元氣"]
{"元氣\n", 0}
終了ステータスが0なので正常です。
ls -1 -a
をElixirから叩いてみます。
コマンドに渡す引数が複数の場合は各引数を別々の文字列として引数リストに入れます。
iex> System.cmd "ls", ["-1", "-a"]
{".\n..\n.elixir_ls\n.formatter.exs\n.git\n.gitignore\nREADME.md\nlib\nmix.exs\nmix.lock\ntest\n",
0}
コマンドに渡す引数がない場合
ls
をElixirから叩いてみます。
コマンドに渡す引数がない場合にも引数リストが必要なようです。
iex> System.cmd "ls"
** (UndefinedFunctionError) function System.cmd/1 is undefined or private. Did you mean:
* cmd/2
* cmd/3
(elixir 1.14.0) System.cmd("ls")
iex:19: (file)
iex> System.cmd "ls", []
{"\n", 0}
ちょっと使い勝手が悪いですが、コマンドインジェクション攻撃を防ぐために意図的に引数が指定された通りにしか解釈されないようにしているようです。
信頼できるコマンドをもっと自由にパイプやリダイレクトなどを使って実行したい場合はSystem.shell/2があります。
System.shell/2
- Elixirのバージョン1.12.0から導入された
- ユーザー入力をこの関数に渡すとコマンドインジェクション攻撃されるリスクがあり危険
ls | sort
をElixirから叩いてみます。
iex> System.shell("ls | sort")
{"README.md\nlib\nmix.exs\nmix.lock\ntest\n", 0}
System.find_executable/1
コマンドを叩く前にそのコマンドが存在するのか確認したい場合はSystem.find_executable/1が便利。
iex> System.find_executable("ls")
"/bin/ls"
iex> System.find_executable("/bin/echo")
"/bin/echo"
iex> System.find_executable("闘魂")
nil
System.cmd/3の中でもコマンドの存在を確認してくれていますが、存在しない場合は:enoent
エラーになります。
iex> System.cmd "foo", []
** (ErlangError) Erlang error: :enoent
(elixir 1.14.0) lib/system.ex:1053: System.cmd("foo", [], [])
iex:33: (file)
Port
System.cmd/3では、PortというElixirの外の世界と対話するための仕組みが利用されています。
ですので、Elixirの提供する仕組みで満足できない場合は、Portを使って自分で実装することも可能だと思います。知らんけど。
Systemモジュールのソースコードを見てみる
Systemモジュールのソースコードを見てみると面白かったです。
- いろんなErlangの関数が活用されている
- パターンマッチでオプションを解析
ご参考までに