Elixirから気軽に外部コマンドを叩く


[Elixir]: https://elixirschool.com/ja/
[System.shell/2]: https://hexdocs.pm/elixir/System.html#shell/2
[System.shell/2（日本語）]: https://hexdocs-pm.translate.goog/elixir/System.html?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=en&_x_tr_pto=wapp#shell/2
[System.cmd/3]: https://hexdocs.pm/elixir/System.html#cmd/3
[System.cmd/3（日本語）]: https://hexdocs-pm.translate.goog/elixir/System.html?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=en&_x_tr_pto=wapp#cmd/3
[System.find_executable/1]: https://hexdocs.pm/elixir/System.html#find_executable/1
[System.find_executable/1（日本語）]: https://hexdocs-pm.translate.goog/elixir/System.html?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=en&_x_tr_pto=wapp#find_executable/1
[Port]: https://hexdocs.pm/elixir/Port.html
[Port（日本語）]: https://hexdocs-pm.translate.goog/elixir/Port.html?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=en&_x_tr_pto=wapp
[終了ステータス]: https://ja.wikipedia.org/wiki/%E7%B5%82%E4%BA%86%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9
[Tuple]: https://hexdocs.pm/elixir/Tuple.html
[インジェクション攻撃]: https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B8%E3%82%A7%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E6%94%BB%E6%92%83
[パイプ (コンピュータ)]: https://ja.wikipedia.org/wiki/%E3%83%91%E3%82%A4%E3%83%97_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF)
[リダイレクト (CLI)]: https://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88_(CLI)

[Elixir]コードから外部コマンドを直接叩けると便利な場合があります。

https://qiita.com/torifukukaiou/items/17d55cf896c24b13350e


## [System.cmd/3]　[:jp:][System.cmd/3（日本語）]

引数
1. `PATH`で使用可能な実行可能なファイル名、またはコマンドの絶対パス
2. コマンドがそのまま引数として受け取れる文字列のリスト

戻り値
- 要素が2個の[タプル][Tuple]
  1. 出力
  2. [終了ステータス]


早速IExを開きます。

```elixir
iex
```

### コマンドに渡す引数がある場合

`echo "元氣"`をElixirから叩いてみます。

```elixir
iex> System.cmd "echo", ["元氣"]
{"元氣\n", 0}
```

[終了ステータス]が0なので正常です。

`ls -1 -a`をElixirから叩いてみます。

コマンドに渡す引数が複数の場合は各引数を別々の文字列として引数リストに入れます。

```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から叩いてみます。

コマンドに渡す引数がない場合にも引数リストが必要なようです。

```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}
```

ちょっと使い勝手が悪いですが、[コマンドインジェクション攻撃][インジェクション攻撃]を防ぐために意図的に引数が指定された通りにしか解釈されないようにしているようです。

信頼できるコマンドをもっと自由に[パイプ][パイプ (コンピュータ)]や[リダイレクト][リダイレクト (CLI)]などを使って実行したい場合は[System.shell/2]があります。

## [System.shell/2]　[:jp:][System.shell/2（日本語）]

- [Elixir]のバージョン1.12.0から導入された
- ユーザー入力をこの関数に渡すと[コマンドインジェクション攻撃][インジェクション攻撃]されるリスクがあり危険

`ls | sort`をElixirから叩いてみます。

```elixir
iex> System.shell("ls | sort")
{"README.md\nlib\nmix.exs\nmix.lock\ntest\n", 0}
```

## [System.find_executable/1]　[:jp:][System.find_executable/1（日本語）]

コマンドを叩く前にそのコマンドが存在するのか確認したい場合は[System.find_executable/1]が便利。

```elixir
iex> System.find_executable("ls")
"/bin/ls"

iex> System.find_executable("/bin/echo")
"/bin/echo"

iex> System.find_executable("闘魂")
nil
```

[System.cmd/3]の中でもコマンドの存在を確認してくれていますが、存在しない場合は`:enoent`エラーになります。

```elixir
iex> System.cmd "foo", []
** (ErlangError) Erlang error: :enoent
    (elixir 1.14.0) lib/system.ex:1053: System.cmd("foo", [], [])
    iex:33: (file)
```

https://github.com/elixir-lang/elixir/blob/0909940b04a3e22c9ea4fedafa2aac349717011c/lib/elixir/lib/system.ex#L1057-L1061

## [Port]　[:jp:][Port（日本語）]

[System.cmd/3]では、[Port]という[Elixir]の外の世界と対話するための仕組みが利用されています。

ですので、[Elixir]の提供する仕組みで満足できない場合は、[Port]を使って自分で実装することも可能だと思います。知らんけど。

## Systemモジュールのソースコードを見てみる

Systemモジュールのソースコードを見てみると面白かったです。

- いろんなErlangの関数が活用されている
- パターンマッチでオプションを解析

https://github.com/elixir-lang/elixir/blob/0909940b04a3e22c9ea4fedafa2aac349717011c/lib/elixir/lib/system.ex

## ご参考までに

https://qiita.com/piacerex/items/e0b6e46b1325bb931122

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf