cURL でダウンロードした対話式シェルスクリプトが bash にパイプ渡しできない

> インタラクティブな（対話型の）シェルスクリプトを、パイプで `bash` or `sh` シェルに渡してユーザに操作させたい。

`yes`/`no` といった入力を求められるスクリプトをダウンロードして[無名パイプ](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))（`|`）で `bash` に渡すと、「`unary operator expected`」と叱られ対話できないなり。

```shell-session:ダウンロード＆実行で失敗する
$ curl -s https://yourdomain.com/your_app/setup.sh | bash
Would you like to set up your_app now? (y/n):bash: line 10: [: =: unary operator expected
```

[「curl ダウンロード 対話 bash スクリプト パイプ渡しできない」とググって](https://www.google.com/search?q=curl+%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89+%E5%AF%BE%E8%A9%B1+bash+%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88+%E3%83%91%E3%82%A4%E3%83%97%E6%B8%A1%E3%81%97%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84)もタイトルからわかる記事がなかったので、自分のググラビリティとして。

## TL; DR （対話的シェルスクリプトのダウンロード実行）

> プロセス置換の機能（[Process Substitution](https://www.gnu.org/software/bash/manual/bash.html#Process-Substitution)）を使ってみる。

```shell
bash <(curl -s https://yourdomain.com/your_app/setup.sh)
```
```bash:応用技（bashシェルスクリプト内でshシェルにスクリプトを渡して実行する）
#!/bin/bash

echo -n '- シェル互換のテスト ...'
/bin/sh <(cat ./sample.sh) 2>/dev/null 1>/dev/null
if [ $? -ne 0 ]; then
  echo 'NG. shシェルと互換がありません'
  exit 1
fi
echo 'OK. shシェル互換です'
```

「`unary operator expected`」エラーは、ユーザーから期待する少なくとも１つ以上の必須項目が足りないために発生するエラーです。リモートファイル（ダウンロードしてきたファイル）をプロセス置換によりローカルファイルのように実行します。

- 参考文献：「[Execute Bash script remotely via cURL](https://stackoverflow.com/a/49679914/8367711)」@ StackOverflow

## TS; DR

一般的に、下記フォーマットでコマンドを打つと `curl` でダウンロードしたスクリプトを `bash` コマンドにパイプで渡し、実行できます。

```shell
curl -s https://yourdomain.com/your_app/setup.sh | bash
```


つまり、ネットに公開されているスクリプトをダウンロードしつつシェルのスクリプトを実行する定番のパターンです。セットアップやインストールなどのスクリプトに利用している人も多いのではないでしょうか。

問題は以下のような、入力を求められる対話式のスクリプトの場合です。

```shell-session:setup.shをローカルで実行
$ # yes が入力値
$ ./setup.sh
Would you like to set up your_app now? (y/n): yes
...
your_app installed! Thank you!
```

上記を同じように、ダウンロード＆実行すると、`yes` もしくは `no` を渡すことができずエラーが発生します。

```shell-session
$ curl -s https://yourdomain.com/your_app/setup.sh | bash
Would you like to set up your_app now? (y/n):bash: line 10: [: =: unary operator expected
```

`[: =: unary operator expected` がエラー内容で、スクリプトの 10 行目に問題があるのがわかります。

### `unary` とは何なり

調べてみたところ、`unary` はラテン語の `una` `uno` などの「１つ」から来ており、「単項の」「１変数の」と言った意味があり、ここでの `operator` は「演算子」という意味です。

つまり `unary operator` とは「[単項演算子](https://ja.wikipedia.org/wiki/%E5%8D%98%E9%A0%85%E6%BC%94%E7%AE%97)」で、入力を１つ受け付けるタイプの[演算子](https://ja.wikipedia.org/wiki/%E6%BC%94%E7%AE%97%E5%AD%90)ということです。

そして、`expected` は「期待していた」という意味なので、何かしらの単項演算子にデータを「渡さないといけない」、もしくは「受け取らないといけない」のに「見当たらないやんけー Σ 👋 `bash`」っと言われているのです。

どうやら総合的に **`unary operator expected` とエラーが出た場合は「少なくとも１つの項目が必要」という意味**に捉えてよさそうです。

このエラーメッセージが出る、よくあるケースが `if` 文などで比較する際に「片方の型が合っていない」もしくは「未定義だった」場合などです。

- [Bash script error [: !=: unary operator expected]](https://stackoverflow.com/questions/22179405/bash-script-error-unary-operator-expected) @ StackOverflow
- [Getting unary operator error on Shell script Linux](https://stackoverflow.com/questions/40243796/getting-unary-operator-error-on-shell-script-linux) @ StackOverflow

今回の件は、スクリプト内の `read` コマンドで標準入力から `yes` や `no` などのデータを受け取らずに処理を続行しているため発生しています。

そうなると回避方法として以下の方法が考えられます。

1. 値がない場合のデフォルトを決める
2. [yesコマンド](https://www.google.com/search?q=site%3Aqiita.com+yes%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%A8%E3%81%AF)を使う
3. 何かしらの方法で `yes` や `no` を渡す

ネットには「[`yes`コマンド](https://ja.wikipedia.org/wiki/Yes_(UNIX))で `yes | ./script` でパイプ渡しにしろ」とあるのですが、うまく動きませんでした。

- [How do I script a “yes” response for installing programs?](https://stackoverflow.com/questions/7642674/how-do-i-script-a-yes-response-for-installing-programs) @ StackOverflow

そもそも、`yes` か `no` かが決め打ちになってしまうため、スクリプトによっては**ユーザーに選択させたい**のです。

> <sup>🐒 &nbsp; ユーザーに選択させず、決め打ちで実行したい場合は以下の記事が参考になります。</sup>
> 
> - <sup>[rustup を非対話的環境でインストールする方法](https://qiita.com/maguro_tuna/items/f69b2e41f753d2ff0cc2) @ Qiita</sup>

すると、StackOverflow に「**`curl ... | bash` の形式だと `read` コマンドは `stdin` からは拾えない**よん」との情報が。

> With the `curl ... | bash` form, bash's stdin is reading the script, so stdin is not available for the `read` command.
> 
> - [Execute Bash script remotely via cURL](https://stackoverflow.com/questions/49679676/execute-bash-script-remotely-via-curl/49679914#49679914) @ StackOverflow

そして丁寧にも解決法まで載せてくれていました。プロセス置換（[Process Substitution](https://www.gnu.org/software/bash/manual/bash.html#Process-Substitution)）と言うのだそうです。

> Try using a Process Substitution to invoke the remote script like a local file:
> 
> `bash <( curl -s ... )`
> 
> - [Execute Bash script remotely via cURL](https://stackoverflow.com/a/49679914/8367711) | comment @ StackOverflow

動いた！バッチリ :thumbsup: :pray: